Bringing TypeScript types at runtime with TypeOnly
source link: https://www.tuicool.com/articles/RfMzuyn
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
With a guy on my team, we recently released a preliminary version of a side-project that could be promising. I wrote this article to present our work. And I start with: Why did we decide to create TypeOnly?
Because it’s not always possible to write DRY code with TypeScript…
TypeScript typing definitions are not available at runtime. Sometime this forces us to repeat ourselves, as in the following example:
type ColorName = "red" | "green" | "blue"
function isColorName(name: string): name is ColorName { return ["red", "green", "blue"].includes(name) }
This kind of code is not ideal. There is an open discussion on Github related to this subject, and the TypeScript team is not ready to provide a solution.
A new language?
TypeOnly is a new language but not a new syntax. TypeOnly aims to be and remain a strict subset of TypeScript: any code that compiles with TypeOnly will also compile with TypeScript. It is the “pure typing” part of TypeScript: only interface
and type
definitions.
See also: A detailed description of the TypeOnly language .
The TypeOnly parser is implemented from scratch and does not require TypeScript as a dependency. It can be used outside a TypeScript project, such as in a JavaScript project, or to check JSON data with a command line tool.
How to check JSON data with TypeOnly (command line version)
Create a file “drawing.d.ts” with the following code:
// drawing.d.ts
export interface Drawing { color: ColorName dashed?: boolean shape: Rectangle | Circle }
export type ColorName = "red" | "green" | "blue"
export interface Rectangle { kind: "rectangle", x: number y: number width: number height: number }
export interface Circle { kind: "circle", x: number y: number radius: number }
Then, create a JSON file “drawing.json” :
{ "color": "green", "shape": { "kind": "circle", "x": 100, "y": 100, "radius": "wrong value" } }
You are ready to check the JSON file:
$ npx @typeonly/checker-cli -s drawing.d.ts -t "Drawing" drawing.json In property 'radius', value "wrong value" is not conform to number.
A mistake is detected in the JSON file. Fix it by replacing the value of the property "radius"
with a valid number. For example: "radius": 50
. And run the command again:
$ npx @typeonly/checker-cli -s drawing.d.ts -t "Drawing" drawing.json The JSON file is conform.
Good. The checker no longer complain.
The TypeOnly tool suite
In the previous section, the file containing types was parsed on the fly. It is a useful feature for a command line checker, but if you need to check several JSONs from your Node.js program, you’ll want to parse the typing once, then reuse the parsed stuff several time. Or, even better: you’ll want to parse the typing definition at compile time, save the parsed result, and then you no longer need the parser at runtime. This is the default way to work with TypeOnly. As a result, using typing metadata is a fast and lightweight process.
TypeOnly currently comes with 4 npm packages:
-
typeonly
: The parser, implemented using ANTLR
, is quite performant and small. It takes
.d.ts
files and generates RTO files (RTO stands for Raw TypeOnly, the file extension is.rto.json
). - @typeonly/reader : A lightweight API that helps to read RTO files.
- @typeonly/checker : A lightweight API that checks JSON or JavaScript data.
- @typeonly/checker-cli : A CLI that uses the parser and the checker on the fly.
Tutorial, part I: Parse TypeOnly code
In this tutorial we’ll see how to use TypeOnly in a JavaScript or a TypeScript project. In a new directory, install typeonly
as a depency:
npm init npm install typeonly --save-dev
Create a subdirectory src/
and copy into it our drawing.d.ts
file given in the example above. Then, edit the file package.json
and add an entry in the section "scripts"
:
"scripts": { "typeonly": "typeonly -o dist-rto/ -s src/" },
Now we can execute the TypeOnly parser via our script:
npm run typeonly
This command creates a file drawing.rto.json
in a new directory dist-rto/
. A RTO ( .rto.json
) file contains all metadata extracted from a .d.ts
typing file. In the next sections we'll see two ways to use this generated RTO file.
Tutorial, part II: Use typing metadata at runtime
In order to navigate through RTO ( .rto.json
) files at runtime, we need the @typeonly/reader
package:
npm install @typeonly/reader
Create a file src/main.js
with the following content:
// src/main.js const { readModules, literals } = require("@typeonly/reader")
async function main() { const modules = await readModules({ modulePaths: ["./drawing"], baseDir: `${__dirname}/../dist-rto` })
const { ColorName } = modules["./drawing"].namedTypes console.log("Color names:", literals(ColorName, "string")) }
main().catch(console.error)
If you write this code in a TypeScript source file, simply replace the require
syntax with a standard import
.
We can execute our program:
$ node src/main.js Color names: [ 'red', 'green', 'blue' ]
Yes, it’s as easy as it seems: the list of color names is now available at runtime.
Notice that at runtime, our code doesn’t depend on the TypeOnly parser. We use @typeonly/reader
which is a lightweight wrapper for .rto.json
files.
Tutorial, part III: How to check JSON data with TypeOnly (API version)
The package @typeonly/checker
is built using @typeonly/reader
. We need it:
npm install @typeonly/checker
Create a file src/check-main.js
with the following content:
// src/check-main.js const { createChecker } = require("@typeonly/checker")
const data = { "color": "green", "shape": { "kind": "circle", "x": 100, "y": 100, "radius": 50 } }
async function main() { const checker = await createChecker({ readModules: { modulePaths: ["./drawing"], baseDir: `${__dirname}/../dist-rto` } }) const result = checker.check("./drawing", "Drawing", data) console.log(result) }
main().catch(console.error)
Execute this new program:
$ node src/check-main.js { valid: true }
As in the previous section, our code doesn’t depend on the TypeOnly parser at runtime. The checker uses the reader which just loads .rto.json
file(s).
Conclusion
What is not covered by this article?
The TypeOnly language is described here . In particular, it allows imports and exports, which means that you can split your typing in several source files.
The APIs of the packages typeonly
, @typeonly/reader
and @typeonly/checker
are not yet well documented. You will need the help of a TypeScript IDE to see all the options and provided data structures.
What’s next?
The help of contributors will be greatly appreciated. For us, TypeOnly is a side project and we don’t have a lot of time for it. However, we plan to work on several subjects in a near future:
/** doc comments */
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK