Skip to content

TypeScript file conversion steps

Edward Faulkner edited this page May 1, 2019 · 6 revisions

We aim to gradually migrate this project from JavaScript to TypeScript.

Step by step

  1. Rename the JavaScript file extension to .ts and commit your change. It's important that the .js file is removed from git before the next step.

  2. Run yarn compile or yarn compile --watch. You should see the built files (.js, .d.ts, and .js.map) created in the file tree. Do not commit them.

  3. Add the built filenames explicitly to the top-level .gitignore file. This step will only work if the old .js file was already removed in a git commit, because .gitignore doesn't apply to files that are already being tracked.

    Any gitignored files will also not be checked by eslint, which is what you want (they are compiled output, not code you authored).

Leaving TODOs

If you skip over any types that should be declared in later refactors, import the todo-any type from @cardstack/plugin-utils/todo-any and use it instead of any. This way, we can easily search for places to update as we change more files into TypeScript. Save any for things that are fine to stay un-typed. Example:

import { todo } from '@cardstack/plugin-utils/todo-any';

interface PrivateOperations {
  sourceId: string;

  // this is an instance of the SourcesUpdate class, 
  // which hasn't been converted to TypeScript yet
  sourcesUpdate: todo;

  nonce: Number | null;
}

JSON:API types

If you're typing a variable that holds a JSON:API document (or part of one), you should use the types from jsonapi-typescript.

CommonJS Compatibility

It's ideal to author TypeScript using ES Modules, as opposed to CommonJS. But TypeScript also has a special syntax for CommonJS compatibility, and you may need it as we do our incremental conversion.

Importing

When importing things (both TS and unconverted JS) into your newly-converted TypeScript file, it's safe to use normal ES module import syntax (because we use the esModuleInterop setting in tsconfig.json):

// the default export works
-const fs = require('fs');
+import fs from 'fs';

// as do named exports
- const { writeFileSync } = require('fs');
+ import { writeFileSync } from 'fs';

Exporting

When exporting things out of your newly-converted TypeScriptFile, you may need to maintain compatibility with existing Javascript files that consume your module via require. There are three main possibilities:

  1. Your original file used only named exports, that people consume via require('./your-module').someName. In this case, you can directly convert to named ES module exports:

    -exports.someUtility = function someUtility() {};
    +export function someUtility() {}
  2. Your original file replaced the entire exports object and your only consumers have already been converted to TypeScript. In this case, you can directly convert to a default ES module export:

    -module.exports = SomeThing;
    +export default SomeThing;
  3. Your original file replaced the entire exports object and you have existing Javascript consumers that use require('your-module). In this case, you need the special compatibility syntax:

    -module.exports = SomeThing;
    +export = SomeThing;

    The point of this syntax is to allow Javascript consumers to keep saying require('./your-module') rather than require('./your-module').default.

When in doubt between 2 and 3, it's safe to always pick 3. We can always come back later once everything is TS and update them all to 2.

Test globals (chai, mocha, expect)

We have a type definition for the global expect that we use in node-tests, but it doesn't yet include the extensions added by prepare-node-tests.js. Once those are needed, we should port prepare-node-tests.js itself to TS, and use that resulting type in types/cardstack__test-support/index.d.ts.

Clone this wiki locally