—declaration and —allowJs

The —declaration flag in TypeScript allows us to generate .d.ts files (declaration files) from TypeScript source files (i.e. .ts and .tsx files).These .d.ts files are important for a couple of reasons.

First of all, they’re important because they allow TypeScript to type-check against other projects without re-checking the original source code.They’re also important because they allow TypeScript to interoperate with existing JavaScript libraries that weren’t built with TypeScript in mind.Finally, a benefit that is often underappreciated: both TypeScript and JavaScript users can benefit from these files when using editors powered by TypeScript to get things like better auto-completion.

Unfortunately, —declaration didn’t work with the —allowJs flag which allows mixing TypeScript and JavaScript input files.This was a frustrating limitation because it meant users couldn’t use the —declaration flag when migrating codebases, even if they were JSDoc-annotated.TypeScript 3.7 changes that, and allows the two options to be used together!

The most impactful outcome of this feature might a bit subtle: with TypeScript 3.7, users can write libraries in JSDoc annotated JavaScript and support TypeScript users.

The way that this works is that when using allowJs, TypeScript has some best-effort analyses to understand common JavaScript patterns; however, the way that some patterns are expressed in JavaScript don’t necessarily look like their equivalents in TypeScript.When declaration emit is turned on, TypeScript figures out the best way to transform JSDoc comments and CommonJS exports into valid type declarations and the like in the output .d.ts files.

As an example, the following code snippet

  1. const assert = require("assert")
  2. module.exports.blurImage = blurImage;
  3. /**
  4. * Produces a blurred image from an input buffer.
  5. *
  6. * @param input {Uint8Array}
  7. * @param width {number}
  8. * @param height {number}
  9. */
  10. function blurImage(input, width, height) {
  11. const numPixels = width * height * 4;
  12. assert(input.length === numPixels);
  13. const result = new Uint8Array(numPixels);
  14. // TODO
  15. return result;
  16. }

Will produce a .d.ts file like

  1. /**
  2. * Produces a blurred image from an input buffer.
  3. *
  4. * @param input {Uint8Array}
  5. * @param width {number}
  6. * @param height {number}
  7. */
  8. export function blurImage(input: Uint8Array, width: number, height: number): Uint8Array;

This can go beyond basic functions with @param tags too, where the following example:

  1. /**
  2. * @callback Job
  3. * @returns {void}
  4. */
  5. /** Queues work */
  6. export class Worker {
  7. constructor(maxDepth = 10) {
  8. this.started = false;
  9. this.depthLimit = maxDepth;
  10. /**
  11. * NOTE: queued jobs may add more items to queue
  12. * @type {Job[]}
  13. */
  14. this.queue = [];
  15. }
  16. /**
  17. * Adds a work item to the queue
  18. * @param {Job} work
  19. */
  20. push(work) {
  21. if (this.queue.length + 1 > this.depthLimit) throw new Error("Queue full!");
  22. this.queue.push(work);
  23. }
  24. /**
  25. * Starts the queue if it has not yet started
  26. */
  27. start() {
  28. if (this.started) return false;
  29. this.started = true;
  30. while (this.queue.length) {
  31. /** @type {Job} */(this.queue.shift())();
  32. }
  33. return true;
  34. }
  35. }

will be transformed into the following .d.ts file:

  1. /**
  2. * @callback Job
  3. * @returns {void}
  4. */
  5. /** Queues work */
  6. export class Worker {
  7. constructor(maxDepth?: number);
  8. started: boolean;
  9. depthLimit: number;
  10. /**
  11. * NOTE: queued jobs may add more items to queue
  12. * @type {Job[]}
  13. */
  14. queue: Job[];
  15. /**
  16. * Adds a work item to the queue
  17. * @param {Job} work
  18. */
  19. push(work: Job): void;
  20. /**
  21. * Starts the queue if it has not yet started
  22. */
  23. start(): boolean;
  24. }
  25. export type Job = () => void;

Note that when using these flags together, TypeScript doesn’t necessarily have to downlevel .js files.If you simply want TypeScript to create .d.ts files, you can use the —emitDeclarationOnly compiler option.

For more details, you can check out the original pull request.