Skip to content

Commit 0508a1a

Browse files
authored
Add format?: "ts" | "js:esm" | "js:cjs" option (#23)
* Add `format?: "ts" | "js:esm" | "js:cjs"` option, closes #12 * Update "Usage" section of README
1 parent a23a3c7 commit 0508a1a

File tree

6 files changed

+83
-10
lines changed

6 files changed

+83
-10
lines changed

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ npm install --save ts-interface-checker
2121

2222
This module works together with [ts-interface-checker](https://github.com/gristlabs/ts-interface-checker) module. You use
2323
`ts-interface-builder` in a build step that converts some TypeScript interfaces
24-
to a new TypeScript file (with `-ti.ts` extension) that provides a runtime
24+
to a new TypeScript or JavaScript file (with `-ti.ts` or `-ti.js` extension) that provides a runtime
2525
description of the interface. You then use `ts-interface-checker` in your
2626
program to create validator functions from this runtime description.
2727

@@ -48,15 +48,18 @@ Then you can generate code for runtime checks with:
4848

4949
It produces a file like this:
5050
```typescript
51-
// foo-ti.js
51+
// foo-ti.ts
5252
import * as t from "ts-interface-checker";
5353

5454
export const Square = t.iface([], {
5555
"size": "number",
5656
"color": t.opt("string"),
5757
});
5858

59-
export default ...;
59+
const exportedTypeSuite: t.ITypeSuite = {
60+
Square,
61+
};
62+
export default exportedTypeSuite;
6063
```
6164

6265
See [ts-interface-checker](https://github.com/gristlabs/ts-interface-checker) module for how to use this file in your program.

lib/index.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import * as fs from "fs";
55
import * as path from "path";
66
import * as ts from "typescript";
77

8+
// Default format to use for `format` option
9+
const defaultFormat = "ts"
810
// Default suffix appended to generated files. Abbreviation for "ts-interface".
911
const defaultSuffix = "-ti";
1012
// Default header prepended to the generated module.
@@ -16,6 +18,7 @@ const defaultHeader =
1618
const ignoreNode = "";
1719

1820
export interface ICompilerOptions {
21+
format?: "ts" | "js:esm" | "js:cjs"
1922
ignoreGenerics?: boolean;
2023
ignoreIndexSignature?: boolean;
2124
inlineImports?: boolean;
@@ -25,7 +28,7 @@ export interface ICompilerOptions {
2528
export class Compiler {
2629
public static compile(
2730
filePath: string,
28-
options: ICompilerOptions = {ignoreGenerics: false, ignoreIndexSignature: false, inlineImports: false},
31+
options: ICompilerOptions = {},
2932
): string {
3033
const createProgramOptions = {target: ts.ScriptTarget.Latest, module: ts.ModuleKind.CommonJS};
3134
const program = ts.createProgram([filePath], createProgramOptions);
@@ -34,6 +37,7 @@ export class Compiler {
3437
if (!topNode) {
3538
throw new Error(`Can't process ${filePath}: ${collectDiagnostics(program)}`);
3639
}
40+
options = {format: defaultFormat, ignoreGenerics: false, ignoreIndexSignature: false, inlineImports: false, ...options}
3741
return new Compiler(checker, options, topNode).compileNode(topNode);
3842
}
3943

@@ -179,7 +183,7 @@ export class Compiler {
179183
const members: string[] = node.members.map(m =>
180184
` "${this.getName(m.name)}": ${getTextOfConstantValue(this.checker.getConstantValue(m))},\n`);
181185
this.exportedNames.push(name);
182-
return `export const ${name} = t.enumtype({\n${members.join("")}});`;
186+
return this._formatExport(name, `t.enumtype({\n${members.join("")}})`);
183187
}
184188
private _compileInterfaceDeclaration(node: ts.InterfaceDeclaration): string {
185189
const name = this.getName(node.name);
@@ -194,15 +198,15 @@ export class Compiler {
194198
}
195199
}
196200
this.exportedNames.push(name);
197-
return `export const ${name} = t.iface([${extend.join(", ")}], {\n${members.join("")}});`;
201+
return this._formatExport(name, `t.iface([${extend.join(", ")}], {\n${members.join("")}})`)
198202
}
199203
private _compileTypeAliasDeclaration(node: ts.TypeAliasDeclaration): string {
200204
const name = this.getName(node.name);
201205
this.exportedNames.push(name);
202206
const compiled = this.compileNode(node.type);
203207
// Turn string literals into explicit `name` nodes, as expected by ITypeSuite.
204208
const fullType = compiled.startsWith('"') ? `t.name(${compiled})` : compiled;
205-
return `export const ${name} = ${fullType};`;
209+
return this._formatExport(name, fullType)
206210
}
207211
private _compileExpressionWithTypeArguments(node: ts.ExpressionWithTypeArguments): string {
208212
return this.compileNode(node.expression);
@@ -231,11 +235,18 @@ export class Compiler {
231235
return this._compileSourceFileStatements(node);
232236
}
233237
// wrap the top node with a default export
238+
if (this.options.format === "js:cjs") {
239+
return `const t = require("ts-interface-checker");\n\n` +
240+
"module.exports = {\n" +
241+
this._compileSourceFileStatements(node) + "\n" +
242+
"};\n"
243+
}
234244
const prefix = `import * as t from "ts-interface-checker";\n` +
235-
"// tslint:disable:object-literal-key-quotes\n\n";
245+
(this.options.format === "ts" ? "// tslint:disable:object-literal-key-quotes\n" : "") +
246+
"\n";
236247
return prefix +
237248
this._compileSourceFileStatements(node) + "\n\n" +
238-
"const exportedTypeSuite: t.ITypeSuite = {\n" +
249+
"const exportedTypeSuite" + (this.options.format === "ts" ? ": t.ITypeSuite" : "") + " = {\n" +
239250
this.exportedNames.map((n) => ` ${n},\n`).join("") +
240251
"};\n" +
241252
"export default exportedTypeSuite;\n";
@@ -248,6 +259,11 @@ export class Compiler {
248259
throw new Error(`Node ${ts.SyntaxKind[node.kind]} not supported by ts-interface-builder: ` +
249260
node.getText());
250261
}
262+
private _formatExport(name: string, expression: string): string {
263+
return this.options.format === "js:cjs"
264+
? ` ${name}: ${this.indent(expression)},`
265+
: `export const ${name} = ${expression};`;
266+
}
251267
}
252268

253269
function getTextOfConstantValue(value: string | number | undefined): string {
@@ -272,6 +288,7 @@ export function main() {
272288
commander
273289
.description("Create runtime validator module from TypeScript interfaces")
274290
.usage("[options] <typescript-file...>")
291+
.option("--format <format>", `Format to use for output; options are 'ts' (default), 'js:esm', 'js:cjs'`)
275292
.option("-g, --ignore-generics", `Ignores generics`)
276293
.option("-i, --ignore-index-signature", `Ignores index signature`)
277294
.option("--inline-imports", `Traverses the full import tree and inlines all types into output`)
@@ -285,6 +302,7 @@ export function main() {
285302
const suffix: string = commander.suffix;
286303
const outDir: string|undefined = commander.outDir;
287304
const options: ICompilerOptions = {
305+
format: commander.format || defaultFormat,
288306
ignoreGenerics: commander.ignoreGenerics,
289307
ignoreIndexSignature: commander.ignoreIndexSignature,
290308
inlineImports: commander.inlineImports,
@@ -300,7 +318,7 @@ export function main() {
300318
// Read and parse the source file.
301319
const ext = path.extname(filePath);
302320
const dir = outDir || path.dirname(filePath);
303-
const outPath = path.join(dir, path.basename(filePath, ext) + suffix + ".ts");
321+
const outPath = path.join(dir, path.basename(filePath, ext) + suffix + (options.format === "ts" ? ".ts" : ".js"));
304322
if (verbose) {
305323
console.log(`Compiling ${filePath} -> ${outPath}`);
306324
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const t = require("ts-interface-checker");
2+
3+
module.exports = {
4+
SomeInterface: t.iface([], {
5+
"foo": "number",
6+
}),
7+
8+
SomeEnum: t.enumtype({
9+
"Foo": 0,
10+
}),
11+
12+
SomeAlias: t.name("number"),
13+
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as t from "ts-interface-checker";
2+
3+
export const SomeInterface = t.iface([], {
4+
"foo": "number",
5+
});
6+
7+
export const SomeEnum = t.enumtype({
8+
"Foo": 0,
9+
});
10+
11+
export const SomeAlias = t.name("number");
12+
13+
const exportedTypeSuite = {
14+
SomeInterface,
15+
SomeEnum,
16+
SomeAlias,
17+
};
18+
export default exportedTypeSuite;

test/fixtures/to-javascript.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export interface SomeInterface {
2+
foo: number
3+
}
4+
5+
export enum SomeEnum { Foo }
6+
7+
export type SomeAlias = number;

test/test_index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,18 @@ describe("ts-interface-builder", () => {
5656
const expected = await readFile(join(fixtures, "imports-parent-shallow-ti.ts"), { encoding: "utf8" });
5757
assert.deepEqual(output, expected);
5858
});
59+
60+
it("should compile to JS in esm module format", async () => {
61+
const output = await Compiler.compile(join(fixtures, "to-javascript.ts"),
62+
{format: 'js:esm'});
63+
const expected = await readFile(join(fixtures, "to-javascript-ti.esm.js"), {encoding: "utf8"});
64+
assert.equal(output, expected);
65+
});
66+
67+
it("should compile to JS in cjs module format", async () => {
68+
const output = await Compiler.compile(join(fixtures, "to-javascript.ts"),
69+
{format: 'js:cjs'});
70+
const expected = await readFile(join(fixtures, "to-javascript-ti.cjs.js"), {encoding: "utf8"});
71+
assert.equal(output, expected);
72+
});
5973
});

0 commit comments

Comments
 (0)