Once, a senior dev force-injected typescript into our unyielding veins. We squirmed a lot and I, for one, was this close to proclaiming it verbose and crapulent twaddle that no codebase could possibly need (least of all ours).
Yet here I am, retrofitting typescript, utterly-avoidably, RETRO-FITTING typescript... into a Javascript project.
'Course, doing things ass-backwardsly from first-principles also happens to be a GREAT way to learn. So do I feel like I learned a lot? To be ENTIRELY HONEST WITH YOU:
...Because the sub-concern that is linting has become an arcane voodoo... and at this point I just traipsed through webspace gathering morcels of "try this" and "but have you done that...?" and mooshed it all together to get something that effing lints and then compiles itself, OK? DO NOT JUDGE ME. Lastly, ymmv with the recipe below because sensitive dependence on initial conditions... not to mention ever-evolving libs, frameworks and standard practices.
tsc
). yarn add typescript -D
.Add or update the src/tsconfig.json
file (which, seems to be json6 and therefore can have comments). Basically, I already had a tsconfig; just had to:
"noEmit": true
setting i.e. stop suppressing the output,outDir
setting (where to put all the compiled, output js files)introduce inclusion and exclusion lists
{
"compilerOptions": {
"target": "ESNEXT", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"allowJs": true, /* Allow javascript files to be compiled. */
"checkJs": true, /* Report errors in .js files. */
"outDir": "./dist", /* Redirect output structure to the directory. */
"strict": false, /* Enable all strict type-checking options. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
"resolveJsonModule": true /* allows for importing, extracting types from and generating .json files */
},
"include": [
"./src/**/*.ts",
"./src/**/*.js",
],
"exclude": [
"**/build/**",
"**/coverage/**",
"**/dist/**",
"**/node_modules/**"
]
}
tests/tsconfig.json
as needed / if needed (you can have tsconfigs tucked away in any folder and they sensibly override each other up the chain)package.json
and make yourself a nice script entry that will run the typescript compiler eg tsc
.In theory that's it, but at this point, I realised that what I ACTUALLY wanted to do, was to slave ts linting concerns to eslint. So:
Hand over ts linting concerns to eslint because, let's face it, it's been around in this linting game for longer and has a feature set to match. This requires an install of both of
@typescript-eslint/eslint-plugin
and@typescript-eslint/parser
...config'd thusly in your eslintrc:
parser: '@typescript-eslint/parser',
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
...
...
]
With prettier
-related plugins always listed last, if you're using them. Even with careful ordering I found the @typescript-eslint/member-delimiter-style
rule kept fighting with prettier over some semi-colons, and I had to override the rule myself. You may run into similar things.
For slaving ts linting to eslint, you will need to tell eslint about your tsconfig, so also add this setting inside the parserOptions block in your .eslintrc
file:
'project': './tsconfig.json'
Now you can make that "lint" script in your package.json a bit beefier: run the typescript compiler to make sure there are no errors, and then run eslint:
"lint": "tsc --noEmit && eslint \"./src/**/*.{js,ts}\" --fix"
For complaints about not finding the module or whatever whenever you do imports without explicit file extensions, you need:
settings: {
"import/resolver": {
"node": {
"extensions": [".js", ".ts", ".d.ts"]
}
}
},
Then you need vscode to get out of the way of your carefully config'd linters, so in your .vscode/settings
file, have this:
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"eslint.validate": [
"javascript",
"typescript",
],
"editor.formatOnSave": true,
"[javascript]": {
"editor.formatOnSave": false
},
"[typescript]": {
"editor.formatOnSave": false
},
}
babel-node
, which will then faceplant over your newly-minted .ts files. So you'll also need to install @babel/preset-typescript
, and add preset-typescript
to the list of presets in your .babelrc
file.That was pretty much the gist of it. You should be good to go!
Fun fact : a .d.ts
contains definitions only (types, interfaces), and as such is a subset of a .ts
file, which may contain other things such as functions, classes, etc.