@eliasku/ts-transformers 0.0.8 → 0.0.9

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
@@ -3,7 +3,7 @@
3
3
 
4
4
  # @eliasku/ts-transformers
5
5
 
6
- TypeScript transformer for aggressive code minification through type-aware property renaming and const enum inlining.
6
+ TypeScript transformer for aggressive code minification through type-aware property renaming.
7
7
 
8
8
  ## Important Requirement
9
9
 
@@ -24,7 +24,7 @@ Two-phase optimization pipeline:
24
24
 
25
25
  Based on type analysis, properties are categorized as:
26
26
 
27
- - **Public (External)**: Exported from entry points → **no prefix** (preserved)
27
+ - **Public**: Exported from entry points → **no prefix** (preserved)
28
28
  - **Private**: Everything else → prefixed with `$_` (mangled by minifier)
29
29
 
30
30
  **Example:**
@@ -52,23 +52,7 @@ class A{publicApi(){},a(){},b=1}
52
52
 
53
53
  ### Const Enum Inlining
54
54
 
55
- Replaces const enum accesses with literal values and transforms declarations to remove codegen, but keep symbol to preserve default import/export flow.
56
-
57
- ```typescript
58
- // Before
59
- const enum Status {
60
- Active = 1,
61
- Inactive = 0,
62
- }
63
- export const status = Status.Active;
64
-
65
- // After transformer
66
- export let Status: {};
67
- export const status = 1;
68
-
69
- // After minifier
70
- export const status=1;
71
- ```
55
+ TypeScript currently inlines const enums, if you use traditional compiler for production build. Check if you have option `isolatedModules: false` disabled in `tsconfig.json`.
72
56
 
73
57
  ## Usage
74
58
 
@@ -165,10 +149,6 @@ class AppComponent {
165
149
  }
166
150
  ```
167
151
 
168
- ### inlineConstEnums (optional, default: true)
169
-
170
- Inline const enum values and transform declarations to preserve export/import flow.
171
-
172
152
  ## Complete Example
173
153
 
174
154
  ```typescript
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.8",
4
+ "version": "0.0.9",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "scripts": {
@@ -40,7 +40,6 @@
40
40
  "plugin",
41
41
  "transformer",
42
42
  "minify",
43
- "const-enum",
44
43
  "treeshake",
45
44
  "mangle"
46
45
  ],
package/src/index.ts CHANGED
@@ -1,6 +1,4 @@
1
1
  import ts from "typescript";
2
- export type { OptimizerOptions } from "./types";
3
-
4
2
  import { ExportsSymbolTree } from "./exports/tracker";
5
3
  import {
6
4
  getDeclarationsForSymbol,
@@ -16,25 +14,17 @@ import {
16
14
  } from "./utils/symbol-utils";
17
15
  import { getNodeJSDocComment } from "./utils/ast-utils";
18
16
  import { OptimizerOptions, defaultOptions, VisibilityType } from "./types";
19
- import { ConstEnumRegistry } from "./const-enum/registry";
20
- import { EnumEvaluator } from "./const-enum/evaluator";
21
- import { isConstEnumType } from "./const-enum/utils";
22
17
  import { LOGS } from "./config";
23
18
 
24
- export const optimizer = (
25
- program: ts.Program,
26
- config?: Partial<OptimizerOptions>,
27
- ): ts.TransformerFactory<ts.SourceFile> => createTransformerFactory(program, config);
19
+ export type { OptimizerOptions } from "./types";
28
20
 
29
- function createTransformerFactory(
21
+ export const optimizer = (
30
22
  program: ts.Program,
31
23
  options?: Partial<OptimizerOptions>,
32
- ): ts.TransformerFactory<ts.SourceFile> {
24
+ ): ts.TransformerFactory<ts.SourceFile> => {
33
25
  const fullOptions: OptimizerOptions = { ...defaultOptions, ...options };
34
26
  const typeChecker = program.getTypeChecker();
35
27
  const exportsSymbolTree = new ExportsSymbolTree(program, fullOptions.entrySourceFiles);
36
- const constEnumRegistry = new ConstEnumRegistry(program);
37
- const enumEvaluator = new EnumEvaluator();
38
28
 
39
29
  const cache = new Map<ts.Symbol, VisibilityType>();
40
30
 
@@ -45,16 +35,6 @@ function createTransformerFactory(
45
35
 
46
36
  return (context: ts.TransformationContext) => {
47
37
  function transformNode(node: ts.Node): ts.Node | undefined {
48
- if (fullOptions.inlineConstEnums !== false) {
49
- if (ts.isPropertyAccessExpression(node)) {
50
- const inlined = tryInlineConstEnum(node);
51
- if (inlined) {
52
- ts.setOriginalNode(inlined, node);
53
- return inlined;
54
- }
55
- }
56
- }
57
-
58
38
  // const a = { node }
59
39
  if (ts.isShorthandPropertyAssignment(node)) {
60
40
  return handleShorthandPropertyAssignment(node);
@@ -636,77 +616,9 @@ function createTransformerFactory(
636
616
  return isSymbolClassMember(typeChecker.getSymbolAtLocation(node));
637
617
  }
638
618
 
639
- function tryInlineConstEnum(node: ts.PropertyAccessExpression): ts.Expression | null {
640
- const expressionType = typeChecker.getTypeAtLocation(node.expression);
641
- if (!isConstEnumType(expressionType)) return null;
642
-
643
- const enumSymbol = expressionType.symbol || expressionType.aliasSymbol;
644
- if (!enumSymbol) return null;
645
-
646
- const enumInfo = constEnumRegistry.getEnumInfo(enumSymbol);
647
- if (!enumInfo) return null;
648
-
649
- const memberValue = enumInfo.members.get(node.name.text)?.value;
650
- if (memberValue === undefined || memberValue === null) return null;
651
- const newNode = enumEvaluator.createLiteral(memberValue);
652
-
653
- const r = ts.setOriginalNode(newNode, node);
654
- ts.setSourceMapRange(newNode, ts.getSourceMapRange(node));
655
- (newNode as any).symbol = (node as any).symbol; // eslint-disable-line @typescript-eslint/no-explicit-any
656
- return r;
657
- }
658
-
659
- return (sourceFile: ts.SourceFile) => {
660
- function handleEnumDeclaration(node: ts.EnumDeclaration): ts.EnumDeclaration | undefined {
661
- if (fullOptions.inlineConstEnums === false) {
662
- return node;
663
- }
664
- if (!hasModifier(node, ts.SyntaxKind.ConstKeyword)) {
665
- return node;
666
- }
667
-
668
- if (sourceFile.isDeclarationFile) {
669
- return ts.factory.updateEnumDeclaration(
670
- node,
671
- node.modifiers?.filter((m) => m.kind !== ts.SyntaxKind.ConstKeyword),
672
- node.name,
673
- node.members,
674
- );
675
- }
676
-
677
- return ts.factory.createVariableStatement(
678
- [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
679
- ts.factory.createVariableDeclarationList(
680
- [
681
- ts.factory.createVariableDeclaration(
682
- node.name, // Reuse enum name
683
- undefined,
684
- ts.factory.createTypeLiteralNode([]), // Empty object type: {}
685
- undefined, // No initializer (empty declaration)
686
- ),
687
- ],
688
- ts.NodeFlags.Let, // Sets the 'const' modifier
689
- ),
690
- ) as unknown as ts.EnumDeclaration;
691
- }
692
-
693
- function wrapTransformNode(node: ts.Node): ts.Node | undefined {
694
- if (ts.isEnumDeclaration(node)) {
695
- const result = handleEnumDeclaration(node);
696
- if (result !== node) {
697
- if (result) {
698
- return transformNode(result);
699
- }
700
- return result;
701
- }
702
- }
703
- return transformNode(node);
704
- }
705
-
706
- const wrappedTransformNodeAndChildren = (node: ts.Node): ts.Node | undefined =>
707
- ts.visitEachChild(wrapTransformNode(node), wrappedTransformNodeAndChildren, context);
619
+ const transformNodeAndChildren = (node: ts.Node): ts.Node | undefined =>
620
+ ts.visitEachChild(transformNode(node), transformNodeAndChildren, context);
708
621
 
709
- return wrappedTransformNodeAndChildren(sourceFile) as ts.SourceFile;
710
- };
622
+ return (sourceFile: ts.SourceFile) => transformNodeAndChildren(sourceFile) as ts.SourceFile;
711
623
  };
712
- }
624
+ };
package/src/types.ts CHANGED
@@ -27,11 +27,6 @@ export interface OptimizerOptions {
27
27
  * A field is treated as "decorated" if itself or any its parent (on type level) has a decorator.
28
28
  */
29
29
  ignoreDecorated: boolean;
30
-
31
- /**
32
- * Whether to inline const enum values.
33
- */
34
- inlineConstEnums?: boolean;
35
30
  }
36
31
 
37
32
  export const enum VisibilityType {
@@ -44,5 +39,4 @@ export const defaultOptions: OptimizerOptions = {
44
39
  privatePrefix: "$_",
45
40
  publicJSDocTag: "public",
46
41
  ignoreDecorated: false,
47
- inlineConstEnums: true,
48
42
  };
@@ -1,158 +0,0 @@
1
- import ts from "typescript";
2
-
3
- export type EnumValue = string | number;
4
-
5
- export interface EvaluationContext {
6
- localMembers: Map<string, EnumValue>;
7
- allowEnumReferences: boolean;
8
- }
9
-
10
- export class EnumEvaluator {
11
- private lastImplicitValue = -1;
12
-
13
- reset(): void {
14
- this.lastImplicitValue = -1;
15
- }
16
-
17
- evaluate(expr: ts.Expression, context: EvaluationContext): EnumValue {
18
- if (ts.isPrefixUnaryExpression(expr)) {
19
- return this.evaluateUnary(expr, context);
20
- } else if (ts.isBinaryExpression(expr)) {
21
- return this.evaluateBinary(expr, context);
22
- } else if (ts.isStringLiteralLike(expr)) {
23
- return expr.text;
24
- } else if (ts.isNumericLiteral(expr)) {
25
- return +expr.text;
26
- } else if (ts.isParenthesizedExpression(expr)) {
27
- return this.evaluate(expr.expression, context);
28
- } else if (ts.isIdentifier(expr)) {
29
- return this.evaluateIdentifier(expr, context);
30
- } else if (
31
- expr.kind === ts.SyntaxKind.TrueKeyword ||
32
- expr.kind === ts.SyntaxKind.FalseKeyword ||
33
- expr.kind === ts.SyntaxKind.NullKeyword
34
- ) {
35
- throw this.createError(expr, `Unsupported literal: ${ts.SyntaxKind[expr.kind]}`);
36
- }
37
-
38
- throw this.createError(expr, `Cannot evaluate expression: ${expr.getText()}`);
39
- }
40
-
41
- evaluateEnumMember(member: ts.EnumMember, context: EvaluationContext): EnumValue {
42
- if (!member.initializer) {
43
- return this.evaluateImplicitMember(member);
44
- }
45
-
46
- const value = this.evaluate(member.initializer, context);
47
-
48
- if (typeof value === "number") {
49
- this.lastImplicitValue = value;
50
- }
51
-
52
- return value;
53
- }
54
-
55
- private evaluateImplicitMember(member: ts.EnumMember): EnumValue {
56
- const name = ts.isIdentifier(member.name) ? member.name.text : `<computed>`;
57
- void name;
58
-
59
- if (this.lastImplicitValue === -1) {
60
- this.lastImplicitValue = 0;
61
- return 0;
62
- }
63
-
64
- const nextValue = this.lastImplicitValue + 1;
65
- this.lastImplicitValue = nextValue;
66
- return nextValue;
67
- }
68
-
69
- createLiteral(value: EnumValue): ts.Expression {
70
- if (typeof value === "string") {
71
- return ts.factory.createStringLiteral(value);
72
- } else {
73
- if (value < 0) {
74
- return ts.factory.createPrefixMinus(ts.factory.createNumericLiteral(Math.abs(value).toString()));
75
- }
76
- return ts.factory.createNumericLiteral(value.toString());
77
- }
78
- }
79
-
80
- private evaluateUnary(expr: ts.PrefixUnaryExpression, context: EvaluationContext): number {
81
- const value = this.evaluate(expr.operand, context);
82
- if (typeof value !== "number") {
83
- throw this.createError(expr, `Unary operator requires numeric value, got ${typeof value}`);
84
- }
85
-
86
- switch (expr.operator) {
87
- case ts.SyntaxKind.PlusToken:
88
- return value;
89
- case ts.SyntaxKind.MinusToken:
90
- return -value;
91
- case ts.SyntaxKind.TildeToken:
92
- return ~value;
93
- default:
94
- throw this.createError(expr, `Unsupported unary operator: ${ts.SyntaxKind[expr.operator]}`);
95
- }
96
- }
97
-
98
- private evaluateBinary(expr: ts.BinaryExpression, context: EvaluationContext): EnumValue {
99
- const left = this.evaluate(expr.left, context);
100
- const right = this.evaluate(expr.right, context);
101
-
102
- if (typeof left === "string" && typeof right === "string" && expr.operatorToken.kind === ts.SyntaxKind.PlusToken) {
103
- return left + right;
104
- }
105
-
106
- if (typeof left === "number" && typeof right === "number") {
107
- switch (expr.operatorToken.kind) {
108
- case ts.SyntaxKind.BarToken:
109
- return left | right;
110
- case ts.SyntaxKind.AmpersandToken:
111
- return left & right;
112
- case ts.SyntaxKind.GreaterThanGreaterThanToken:
113
- return left >> right;
114
- case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
115
- return left >>> right;
116
- case ts.SyntaxKind.LessThanLessThanToken:
117
- return left << right;
118
- case ts.SyntaxKind.CaretToken:
119
- return left ^ right;
120
- case ts.SyntaxKind.AsteriskToken:
121
- return left * right;
122
- case ts.SyntaxKind.SlashToken:
123
- return left / right;
124
- case ts.SyntaxKind.PlusToken:
125
- return left + right;
126
- case ts.SyntaxKind.MinusToken:
127
- return left - right;
128
- case ts.SyntaxKind.PercentToken:
129
- return left % right;
130
- case ts.SyntaxKind.AsteriskAsteriskToken:
131
- return left ** right;
132
- default:
133
- throw this.createError(expr, `Unsupported binary operator: ${ts.SyntaxKind[expr.operatorToken.kind]}`);
134
- }
135
- }
136
-
137
- throw this.createError(expr, `Cannot evaluate binary expression with types ${typeof left} and ${typeof right}`);
138
- }
139
-
140
- private evaluateIdentifier(expr: ts.Identifier, context: EvaluationContext): EnumValue {
141
- if (!context.allowEnumReferences) {
142
- throw this.createError(expr, `Cannot reference enum member here`);
143
- }
144
-
145
- const value = context.localMembers.get(expr.text);
146
- if (value === undefined) {
147
- throw this.createError(expr, `Undefined enum member: ${expr.text}`);
148
- }
149
-
150
- return value;
151
- }
152
-
153
- private createError(node: ts.Node, message: string): Error {
154
- const sourceFile = node.getSourceFile();
155
- const pos = sourceFile.getLineAndCharacterOfPosition(node.getStart());
156
- return new Error(`${sourceFile.fileName}:${pos.line + 1}:${pos.character}: ${message}`);
157
- }
158
- }
@@ -1,152 +0,0 @@
1
- import ts from "typescript";
2
- import { EnumValue, EvaluationContext } from "./evaluator";
3
- import { EnumEvaluator } from "./evaluator";
4
- import { isConstEnumSymbol } from "./utils";
5
- import { hasModifier } from "../utils/symbol-utils";
6
-
7
- export interface ConstEnumInfo {
8
- declaration: ts.EnumDeclaration;
9
- members: Map<string, ConstEnumMemberInfo>;
10
- isExported: boolean;
11
- sourceFile: ts.SourceFile;
12
- }
13
-
14
- export interface ConstEnumMemberInfo {
15
- declaration: ts.EnumMember;
16
- name: string;
17
- value: EnumValue | null;
18
- }
19
-
20
- export class ConstEnumRegistry {
21
- private readonly program: ts.Program;
22
- private readonly typeChecker: ts.TypeChecker;
23
- private readonly enumDeclarations: Map<string, ConstEnumInfo>;
24
-
25
- constructor(program: ts.Program) {
26
- this.program = program;
27
- this.typeChecker = program.getTypeChecker();
28
- this.enumDeclarations = new Map();
29
- this.collectConstEnumsFromEntryPoints();
30
- }
31
-
32
- getEnum(enumName: string): ConstEnumInfo | undefined {
33
- return this.enumDeclarations.get(enumName);
34
- }
35
-
36
- getEnumInfo(symbol: ts.Symbol): ConstEnumInfo | undefined {
37
- const name = this.getEnumSymbolName(symbol);
38
- return this.enumDeclarations.get(name);
39
- }
40
-
41
- getMemberValue(enumName: string, memberName: string): EnumValue | undefined {
42
- const enumInfo = this.enumDeclarations.get(enumName);
43
- if (!enumInfo) return undefined;
44
- const memberInfo = enumInfo.members.get(memberName);
45
- return memberInfo?.value ?? undefined;
46
- }
47
-
48
- getAllEnums(): ConstEnumInfo[] {
49
- return Array.from(this.enumDeclarations.values());
50
- }
51
-
52
- getEnumCount(): number {
53
- return this.enumDeclarations.size;
54
- }
55
-
56
- private collectConstEnumsFromEntryPoints(): void {
57
- const sourceFiles = this.program.getSourceFiles();
58
-
59
- for (const sourceFile of sourceFiles) {
60
- if (sourceFile.isDeclarationFile) {
61
- continue;
62
- }
63
-
64
- this.registerConstEnumFromSource(sourceFile);
65
- }
66
- }
67
-
68
- private registerConstEnumFromSource(sourceFile: ts.SourceFile): void {
69
- ts.forEachChild(sourceFile, (node) => {
70
- if (ts.isEnumDeclaration(node) && hasModifier(node, ts.SyntaxKind.ConstKeyword)) {
71
- const symbol = this.typeChecker.getSymbolAtLocation(node.name);
72
- if (symbol && isConstEnumSymbol(symbol)) {
73
- this.registerEnum(symbol, node, sourceFile);
74
- }
75
- }
76
- });
77
- }
78
-
79
- private registerEnum(symbol: ts.Symbol, declaration: ts.EnumDeclaration, sourceFile: ts.SourceFile): void {
80
- const name = this.getEnumSymbolName(symbol);
81
-
82
- if (this.enumDeclarations.has(name)) {
83
- return;
84
- }
85
-
86
- const isExported = this.hasExportModifier(declaration);
87
-
88
- const enumInfo: ConstEnumInfo = {
89
- declaration,
90
- members: new Map(),
91
- isExported,
92
- sourceFile,
93
- };
94
-
95
- this.evaluateEnumMembers(enumInfo);
96
-
97
- this.enumDeclarations.set(name, enumInfo);
98
- }
99
-
100
- private hasExportModifier(node: ts.Node): boolean {
101
- if (!ts.canHaveModifiers(node)) {
102
- return false;
103
- }
104
- const modifiers = ts.getModifiers(node);
105
- if (!modifiers) {
106
- return false;
107
- }
108
- return modifiers.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
109
- }
110
-
111
- private evaluateEnumMembers(enumInfo: ConstEnumInfo): void {
112
- const evaluator = new EnumEvaluator();
113
- evaluator.reset();
114
- const context: EvaluationContext = {
115
- localMembers: new Map(),
116
- allowEnumReferences: true,
117
- };
118
-
119
- for (const member of enumInfo.declaration.members) {
120
- if (!ts.isIdentifier(member.name)) continue;
121
-
122
- try {
123
- const value = evaluator.evaluateEnumMember(member, context);
124
- context.localMembers.set(member.name.text, value);
125
- enumInfo.members.set(member.name.text, {
126
- declaration: member,
127
- name: member.name.text,
128
- value,
129
- });
130
- } catch (error) {
131
- throw new Error(
132
- `Failed to evaluate const enum member ${enumInfo.declaration.name.text}.${member.name.text}: ${error}`,
133
- );
134
- }
135
- }
136
- }
137
-
138
- private getEnumDeclaration(symbol: ts.Symbol): ts.EnumDeclaration | null {
139
- const decl = symbol.declarations?.[0];
140
- if (decl && ts.isEnumDeclaration(decl)) {
141
- return decl;
142
- }
143
- return null;
144
- }
145
-
146
- private getEnumSymbolName(symbol: ts.Symbol): string {
147
- if (symbol.flags & ts.SymbolFlags.Alias) {
148
- return this.typeChecker.getAliasedSymbol(symbol).name;
149
- }
150
- return symbol.name;
151
- }
152
- }
@@ -1,13 +0,0 @@
1
- import ts from "typescript";
2
-
3
- export const isConstEnumSymbol = (symbol: ts.Symbol): boolean => (symbol.flags & ts.SymbolFlags.ConstEnum) !== 0;
4
-
5
- export const isConstEnumType = (type: ts.Type | undefined): boolean => {
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
- };