TypeScript

From mediawiki.org

This article is about using TypeScript in the MediaWiki ecosystem. There are many excellent resources for general TypeScript usage elsewhere, some are referenced below.

TypeScript as a JavaScript documentation linter[edit]

The TypeScript executable, tsc, can be used to lint the documentation in plain JavaScript files.

JSDoc typing[edit]

JSDoc is the preferred way to write JavaScript documentation in MediaWiki software (JSDuck is deprecated). TypeScript can parse and understand JSDocs. A typical type would be documented as:

/**
 * Template properties for a portlet.
 * @typedef {Object} PortletContext
 * @prop {string} portal-id Identifier for wrapper.
 * @prop {string} html-tooltip Tooltip message.
 * @prop {string} msg-label-id Aria identifier for label text.
 * @prop {string} [html-userlangattributes] Additional Element attributes.
 * @prop {string} msg-label Aria label text.
 * @prop {string} html-portal-content
 * @prop {string} [html-after-portal] HTML to inject after the portal.
 * @prop {string} [html-hook-vector-after-toolbox] Deprecated and used by the toolbox portal.
 */

And a function would be:

/**
 * @param {PortletContext} data The properties to render the portlet template with.
 * @return {HTMLElement} The rendered portlet.
 */
function wrapPortlet( data ) {
    const node = document.createElement( 'div' );
    node.setAttribute( 'id', 'mw-panel' );
    node.innerHTML = mustache.render( portalTemplate, data );
    return node;
}

The above examples on the TypeScript playground.

Sharing types across files[edit]

  • Type definitions (*.d.ts) are global scripts by default.
  • Type definitions become non-global modules if the file contains any imports or exports. Any types you wish to be globals must be explicitly made so. E.g:
declare global {
  interface XY {x: number, y: number}
}
  • requires and imports bring along their types.
  • Import types look like /** @typedef {import('../../math/xy').XY} XY */.

TypeScript definitions[edit]

TypeScript definitions are a common means to describe and share the documentation and typing information for an entire library such as jQuery. They're also useful more broadly for sharing definitions across files and globals.

An equivalent TypeScript definition (or TSD) of the above type and function (with documentation and implementation stripped) is:

interface PortletContext {
    'portal-id': string;
    'html-tooltip': string;
    'msg-label-id': string;
    'html-userlangattributes'?: string;
    'msg-label': string;
    'html-portal-content': string;
    'html-after-portal'?: string;
    'html-hook-vector-after-toolbox'?: string;
}

declare function wrapPortlet(data: PortletContext): HTMLElement;

The above examples on the TypeScript playground.

Documentation in TSDs are still JSDocs so it could be identical to the preceding examples (or omit the JSDoc typing). However, TSDs cannot contain implementation as they are not compiled and have no functional impact.

MediaWiki TypeScript definitions[edit]

A library exists for documenting MediaWiki core and other common libraries.

TypeScript as a compiler[edit]

TypeScript also supports more direct means to describe typing information called annotations. However, this syntax requires TypeScript proper and a compilation step. Examples include (in roughly reverse chronological order):

Project setup[edit]

For projects with mixed JavaScript and TypeScript files or only plain JavaScript files, you may wish to include or exclude files until their typing is fully accurate with respect to the documentation. There are several ways to do that including:

  • tsconfig.json: add files via the includes or remove files via the excludes field. More details can be found under compiler options.
  • @ts-check / @ts-nocheck: add or remove entire files with a single comment.
  • @ts-ignore: ignore the errors on a single line. Should generally be used as a temporary fix pointing to a Phabricator ticket, as this can (and has)led to production errors.

Example JavaScript documentation configuration[edit]

{
	"exclude": ["docs", "vendor"],
	"compilerOptions": {
		"resolveJsonModule": true,
		"esModuleInterop": true,
		"strict": true,
		"noUnusedLocals": true,
		"noUnusedParameters": true,
		"noImplicitReturns": true,
		"newLine": "lf",
		"forceConsistentCasingInFileNames": true,
		"pretty": true,
		"target": "es5",
		"lib": ["dom"],
		"allowJs": true,
		"checkJs": true,
		"noEmit": true
	}
}

Complete project example.

See also[edit]