Skip to content

Commit 1da5c82

Browse files
committed
pure classes
1 parent a1d8a5a commit 1da5c82

File tree

7 files changed

+152
-35
lines changed

7 files changed

+152
-35
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,16 @@ tsc-multi --clean
7272

7373
### `targets`
7474

75-
Build targets. All options except `extname` and `shareHelpers` will override `compilerOptions` in `tsconfig.json`.
75+
Build targets. All options except `extname`, `shareHelpers`, and `pureClassAssignment` will override `compilerOptions` in `tsconfig.json`.
7676

7777
```js
7878
{
7979
// Rename the extension of output files
8080
extname: ".js",
8181
// Emit all helpers to one file
8282
shareHelpers: "helpers.js",
83+
// Wrap static assignments to a class in @__PURE__ comments
84+
pureClassAssignment: true,
8385
// Skip type-checking (Experimental)
8486
transpileOnly: false,
8587
// Compiler options

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "tsc-multi",
3-
"version": "1.1.4",
3+
"version": "1.1.5",
44
"description": "Compile multiple TypeScript projects into multiple targets.",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

src/build.ts

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -103,24 +103,36 @@ export async function build({
103103
const reportStyles = getReportStyles();
104104

105105
const codes = await pAll(
106-
targets.map(({ extname, transpileOnly, shareHelpers, ...target }, i) => {
107-
const prefix = `[${trimPrefix(extname || DEFAULT_EXTNAME, ".")}]: `;
108-
const prefixStyle = reportStyles[i % reportStyles.length];
109-
110-
return () => {
111-
return runWorker({
112-
...options,
113-
projects,
114-
stdout,
115-
stderr,
106+
targets.map(
107+
(
108+
{
116109
extname,
117-
shareHelpers,
118-
target,
119-
reportPrefix: prefixStyle(prefix),
120110
transpileOnly,
121-
});
122-
};
123-
}),
111+
shareHelpers,
112+
pureClassAssignment,
113+
...target
114+
},
115+
i
116+
) => {
117+
const prefix = `[${trimPrefix(extname || DEFAULT_EXTNAME, ".")}]: `;
118+
const prefixStyle = reportStyles[i % reportStyles.length];
119+
120+
return () => {
121+
return runWorker({
122+
...options,
123+
projects,
124+
stdout,
125+
stderr,
126+
extname,
127+
shareHelpers,
128+
pureClassAssignment,
129+
target,
130+
reportPrefix: prefixStyle(prefix),
131+
transpileOnly,
132+
});
133+
};
134+
}
135+
),
124136
{ concurrency: maxWorkers }
125137
);
126138

src/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const debug = Debug.extend("config");
2020
const targetSchema = type({
2121
extname: optional(string()),
2222
shareHelpers: optional(string()),
23+
pureClassAssignment: optional(boolean()),
2324
transpileOnly: optional(boolean()),
2425
});
2526

Lines changed: 111 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
import { resolve, dirname, extname, relative } from "path";
22
import type ts from "typescript";
3-
import { trimSuffix } from "../utils";
3+
import { trimSuffix } from "./utils";
44
import assert from "assert";
5-
import {
6-
isExternalModuleReference,
7-
isImportEqualsDeclaration,
8-
} from "typescript";
95

106
const JS_EXT = ".js";
117
const JSON_EXT = ".json";
@@ -14,9 +10,10 @@ function isRelativePath(path: string): boolean {
1410
return path.startsWith("./") || path.startsWith("../");
1511
}
1612

17-
export interface RewriteImportTransformerOptions {
13+
export interface TransformerOptions {
1814
extname: string;
1915
getResolvedShareHelpers(): string | undefined;
16+
pureClassAssignment: boolean;
2017
helpersNeeded: Set<string>;
2118
system: ts.System;
2219
ts: typeof ts;
@@ -32,9 +29,9 @@ function nodeIsSynthesized(range: ts.TextRange): boolean {
3229
return positionIsSynthesized(range.pos) || positionIsSynthesized(range.end);
3330
}
3431

35-
export function createRewriteImportTransformer<
36-
T extends ts.SourceFile | ts.Bundle
37-
>(options: RewriteImportTransformerOptions): ts.TransformerFactory<T> {
32+
export function createTransformer<T extends ts.SourceFile | ts.Bundle>(
33+
options: TransformerOptions
34+
): ts.TransformerFactory<T> {
3835
const {
3936
sys,
4037
factory,
@@ -146,8 +143,10 @@ export function createRewriteImportTransformer<
146143
(isImportDeclaration(original)
147144
? isStringLiteral(original.moduleSpecifier) &&
148145
original.moduleSpecifier.text === "tslib"
149-
: isImportEqualsDeclaration(original) &&
150-
isExternalModuleReference(original.moduleReference) &&
146+
: options.ts.isImportEqualsDeclaration(original) &&
147+
options.ts.isExternalModuleReference(
148+
original.moduleReference
149+
) &&
151150
isStringLiteral(original.moduleReference.expression) &&
152151
original.moduleReference.expression.text === "tslib")
153152
) {
@@ -219,15 +218,114 @@ export function createRewriteImportTransformer<
219218
return visitEachChild(node, visitor, ctx);
220219
};
221220

221+
const pureClassAssignment = (sourceFile: ts.SourceFile) => {
222+
if (!options.pureClassAssignment) return sourceFile;
223+
const newStatements = [];
224+
const classes: Record<
225+
string,
226+
[
227+
ts.ClassDeclaration & { name: ts.Identifier },
228+
...ts.ExpressionStatement[]
229+
]
230+
> = Object.create(null);
231+
for (const statement of sourceFile.statements) {
232+
if (
233+
options.ts.isClassDeclaration(statement) &&
234+
statement.name &&
235+
!classes[statement.name.text]
236+
) {
237+
newStatements.push(
238+
(classes[statement.name.text] = [
239+
statement as typeof statement & { name: ts.Identifier },
240+
])
241+
);
242+
continue;
243+
} else if (
244+
options.ts.isExpressionStatement(statement) &&
245+
options.ts.isBinaryExpression(statement.expression) &&
246+
statement.expression.operatorToken.kind === SyntaxKind.EqualsToken
247+
) {
248+
if (
249+
options.ts.isPropertyAccessExpression(statement.expression.left) &&
250+
options.ts.isIdentifier(statement.expression.left.expression) &&
251+
classes[statement.expression.left.expression.text]
252+
) {
253+
classes[statement.expression.left.expression.text].push(statement);
254+
continue;
255+
} else if (
256+
options.ts.isIdentifier(statement.expression.left) &&
257+
options.ts.isIdentifier(statement.expression.right) &&
258+
classes[statement.expression.right.text] &&
259+
(statement.expression.left as any)?.emitNode?.autoGenerate
260+
) {
261+
// _a = Cloudflare;
262+
// Cloudflare.Cloudflare = _a;
263+
classes[statement.expression.right.text].push(statement);
264+
continue;
265+
} else if (options.ts.isIdentifier(statement.expression.left)) {
266+
// _BaseCloudflare_encoder = new WeakMap();
267+
const cls = (statement.expression.left as any)?.emitNode
268+
?.autoGenerate?.prefix?.node?.text;
269+
if (classes[cls]) {
270+
classes[cls].push(statement);
271+
}
272+
continue;
273+
}
274+
}
275+
newStatements.push(statement);
276+
}
277+
return ctx.factory.updateSourceFile(
278+
sourceFile,
279+
newStatements.map((group) =>
280+
Array.isArray(group)
281+
? group.length === 1 &&
282+
!group[0].members.some(
283+
(member) =>
284+
member.name?.kind === SyntaxKind.ComputedPropertyName
285+
)
286+
? group[0]
287+
: ctx.factory.createVariableStatement(group[0].modifiers, [
288+
ctx.factory.createVariableDeclaration(
289+
group[0].name.text,
290+
undefined,
291+
undefined,
292+
options.ts.addSyntheticLeadingComment(
293+
ctx.factory.createImmediatelyInvokedArrowFunction([
294+
ctx.factory.updateClassDeclaration(
295+
group[0],
296+
[],
297+
group[0].name,
298+
group[0].typeParameters,
299+
group[0].heritageClauses,
300+
group[0].members
301+
),
302+
...group.slice(1),
303+
ctx.factory.createReturnStatement(
304+
ctx.factory.createIdentifier(group[0].name.text)
305+
),
306+
]),
307+
SyntaxKind.MultiLineCommentTrivia,
308+
" @__PURE__ ",
309+
false
310+
)
311+
),
312+
])
313+
: group
314+
)
315+
);
316+
};
317+
222318
return (file) => {
223319
if (options.ts.isSourceFile(file)) {
224320
sourceFile = file;
225-
return visitNode(file, visitor) as any;
321+
return pureClassAssignment(visitNode(file, visitor) as ts.SourceFile);
226322
} else if (options.ts.isBundle(file)) {
227323
return ctx.factory.createBundle(
228324
file.sourceFiles.map((file) => {
229325
sourceFile = file;
230-
return visitNode(file, visitor) as ts.SourceFile;
326+
return pureClassAssignment(
327+
visitNode(file, visitor) as ts.SourceFile
328+
);
231329
})
232330
) as any;
233331
} else {

src/worker/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export interface WorkerOptions {
44
target: Omit<Target, "extname">;
55
extname?: string;
66
shareHelpers?: string;
7+
pureClassAssignment?: boolean;
78
verbose?: boolean;
89
dry?: boolean;
910
force?: boolean;

src/worker/worker.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
trimSuffix,
77
isIncrementalCompilation,
88
} from "../utils";
9-
import { createRewriteImportTransformer } from "../transformers/rewriteImport";
9+
import { createTransformer } from "../transformer";
1010
import { WorkerOptions } from "./types";
1111
import { dirname, extname, join, resolve } from "path";
1212
import assert from "assert";
@@ -252,17 +252,19 @@ export class Worker {
252252

253253
const transformers: ts.CustomTransformers = {
254254
after: [
255-
createRewriteImportTransformer({
255+
createTransformer({
256256
extname: this.data.extname || JS_EXT,
257+
pureClassAssignment: this.data.pureClassAssignment || false,
257258
getResolvedShareHelpers: () => resolvedShareHelpers,
258259
helpersNeeded,
259260
system: this.system,
260261
ts: this.ts,
261262
}),
262263
],
263264
afterDeclarations: [
264-
createRewriteImportTransformer({
265+
createTransformer({
265266
extname: this.data.extname || JS_EXT,
267+
pureClassAssignment: this.data.pureClassAssignment || false,
266268
getResolvedShareHelpers: () => resolvedShareHelpers,
267269
helpersNeeded,
268270
system: this.system,
@@ -427,12 +429,13 @@ export class Worker {
427429
// TODO: Merge custom transformers
428430
const transformers: ts.CustomTransformers = {
429431
after: [
430-
createRewriteImportTransformer({
432+
createTransformer({
431433
extname: this.data.extname || JS_EXT,
432434
getResolvedShareHelpers: () => resolvedShareHelpers,
433435
helpersNeeded,
434436
system: this.system,
435437
ts: this.ts,
438+
pureClassAssignment: this.data.pureClassAssignment || false,
436439
}),
437440
],
438441
};

0 commit comments

Comments
 (0)