I'm using a lot of libraries both my own and 3rd party. I see the "typings" directory contains some for Jquery and WinRT... but how are they created?
There are a few options available for you depending on the library in question, how it's written, and what level of accuracy you're looking for. Let's review the options, in roughly descending order of desirability.
Maybe It Exists Already
Always check DefinitelyTyped (https://github.com/DefinitelyTyped/DefinitelyTyped) first. This is a community repo full of literally thousands of .d.ts files and it's very likely the thing you're using is already there. You should also check TypeSearch (https://microsoft.github.io/TypeSearch/) which is a search engine for NPM-published .d.ts files; this will have slightly more definitions than DefinitelyTyped. A few modules are also shipping their own definitions as part of their NPM distribution, so also see if that's the case before trying to write your own.
Maybe You Don't Need One
TypeScript now supports the --allowJs
flag and will make more JS-based inferences in .js files. You can try including the .js file in your compilation along with the --allowJs
setting to see if this gives you good enough type information. TypeScript will recognize things like ES5-style classes and JSDoc comments in these files, but may get tripped up if the library initializes itself in a weird way.
Get Started With --allowJs
If --allowJs
gave you decent results and you want to write a better definition file yourself, you can combine --allowJs
with --declaration
to see TypeScript's "best guess" at the types of the library. This will give you a decent starting point, and may be as good as a hand-authored file if the JSDoc comments are well-written and the compiler was able to find them.
Get Started with dts-gen
If --allowJs
didn't work, you might want to use dts-gen (https://github.com/Microsoft/dts-gen) to get a starting point. This tool uses the runtime shape of the object to accurately enumerate all available properties. On the plus side this tends to be very accurate, but the tool does not yet support scraping the JSDoc comments to populate additional types. You run this like so:
npm install -g dts-gen
dts-gen -m <your-module>
This will generate your-module.d.ts
in the current folder.
Hit the Snooze Button
If you just want to do it all later and go without types for a while, in TypeScript 2.0 you can now write
declare module "foo";
which will let you import
the "foo"
module with type any
. If you have a global you want to deal with later, just write
declare const foo: any;
which will give you a foo
variable.
--declarations
generates both the .js
file and the .d.ts
file, which means you only need to run a single compile. –
Cassiani --allowJs
with --declaration
options can't be combined (tested in TypeScript 1.8 and 2.0). If I try, I get: error TS5053: Option 'allowJs' cannot be specified with option 'declaration'
–
Olander You can either use tsc --declaration fileName.ts
like Ryan describes, or you can specify declaration: true
under compilerOptions
in your tsconfig.json
assuming you've already had a tsconfig.json
under your project.
tsc --declaration test.ts
I get the error Cannot find name...
for the types that I'm trying to create a declaration file for :) So I need the types before I can declare them? –
Tearful declaration: true
to your tsconfig.json
file? –
Chrono When creating your own library, you can can create *.d.ts
files by using the tsc
(TypeScript Compiler) command like so:
(assuming you're building your library to the dist/lib
folder)
tsc -d --declarationDir dist/lib --declarationMap --emitDeclarationOnly
-d
(--declaration
): generates the*.d.ts
files--declarationDir dist/lib
: Output directory for generated declaration files.--declarationMap
: Generates a sourcemap for each corresponding ‘.d.ts’ file.--emitDeclarationOnly
: Only emit ‘.d.ts’ declaration files. (no compiled JS)
(see the docs for all command line compiler options)
Or for instance in your package.json
:
"scripts": {
"build:types": "tsc -d --declarationDir dist/lib --declarationMap --emitDeclarationOnly",
}
and then run: yarn build:types
(or npm run build:types
)
d.ts
files and be able to use interfaces. Do you have any examples? –
Dinsmore *.d.ts
files and put them in the dist/lib
folder. You do need a tsconfig.json
file in the root of your project, but that should be there for the project to work anyway. –
Izy tsconfig.json
allows you to define those options in a file, so you could have { "include": ["src/**/*"], "compilerOptions": { "allowJs": true, "declaration": true, "emitDeclarationOnly": true, "outDir": "dist/lib", "declarationMap": true }}
and running tsc
on its own would do the same as you command line options. –
Recapture As Ryan says, the tsc compiler has a switch --declaration
which generates a .d.ts
file from a .ts
file. Also note that (barring bugs) TypeScript is supposed to be able to compile Javascript, so you can pass existing javascript code to the tsc compiler.
The best way to deal with this (if a declaration file is not available on DefinitelyTyped) is to write declarations only for the things you use rather than the entire library. This reduces the work a lot - and additionally the compiler is there to help out by complaining about missing methods.
as described in http://channel9.msdn.com/posts/Anders-Hejlsberg-Steve-Lucco-and-Luke-Hoban-Inside-TypeScript at 00:33:52 they had built a tool to convert WebIDL and WinRT metadata into TypeScript d.ts
To make things extremely simple for TypeScript newbies like me:
tsc --declaration FileName.js --allowJs
This command will generate a d.ts file in your current folder. You need to have TypeScript installed, but you don't need to be the project maintainer, and you don't need to have a larger compilation process set up. You can hand it any old JavaScript file and it will attempt to generate type definitions for you.
Here is some PowerShell that creates a single TypeScript definition file a library that includes multiple *.js
files with modern JavaScript.
First, change all the extensions to .ts
.
Get-ChildItem | foreach { Rename-Item $_ $_.Name.Replace(".js", ".ts") }
Second, use the TypeScript compiler to generate definition files. There will be a bunch of compiler errors, but we can ignore those.
Get-ChildItem | foreach { tsc $_.Name }
Finally, combine all the *.d.ts
files into one index.d.ts
, removing the import
statements and removing the default
from each export statement.
Remove-Item index.d.ts;
Get-ChildItem -Path *.d.ts -Exclude "Index.d.ts" | `
foreach { Get-Content $_ } | `
where { !$_.ToString().StartsWith("import") } | `
foreach { $_.Replace("export default", "export") } | `
foreach { Add-Content index.d.ts $_ }
This ends with a single, usable index.d.ts
file that includes many of the definitions.
I would look for an existing mapping of your 3rd party JS libraries that support Script# or SharpKit. Users of these C# to .js cross compilers will have faced the problem you now face and might have published an open source program to scan your 3rd party lib and convert into skeleton C# classes. If so hack the scanner program to generate TypeScript in place of C#.
Failing that, translating a C# public interface for your 3rd party lib into TypeScript definitions might be simpler than doing the same by reading the source JavaScript.
My special interest is Sencha's ExtJS RIA framework and I know there have been projects published to generate a C# interpretation for Script# or SharpKit
This answer shows how to do it in a stand alone function using the typescript compiler API:
import ts from 'typescript';
function createDTsTextsFromJsFilePaths(
jsFileNames: string[]
): Record<string, string> {
const options = {
allowJs: true,
declaration: true,
emitDeclarationOnly: true,
};
// Create a Program with an in-memory emit
const createdFiles: Record<string, string> = {};
const host = ts.createCompilerHost(options);
host.writeFile = (fileName: string, contents: string): void => {
const dtsName = fileName.replace('.js', '.d.ts');
createdFiles[dtsName] = contents;
};
// Prepare and emit the d.ts files
const program = ts.createProgram(jsFileNames, options, host);
program.emit();
return createdFiles;
}
////////////// test code below here ///////////////////////////////////
import fs from 'fs'
const jsTest =
`
"use strict"
function x(){
return {
a: 1,
b: ()=>'hello'
}
}
`;
const jsPath = '/tmp/jstest.js';
fs.writeFileSync(jsPath, jsTest);
const createdFiles = createDTsTextsFromJsFilePaths([jsPath]);
for (const dtsName in createdFiles) {
console.log('================');
console.log(dtsName);
console.log('----------------');
console.log(createdFiles[dtsName]);
}
Execution of the test gives the result
================
/tmp/jstest.d.ts
----------------
declare function x(): {
a: number;
b: () => string;
};
The function is derived from documentation in the type script compiler API guide
© 2022 - 2024 — McMap. All rights reserved.