This repository contains various TypeScript type definitions to make working with TypeScript more convenient.
This project goes along with TypeScript types for new JavaScript, which contains TypeScript type definitions for new JavaScript stuff that isn't in TypeScript's standard type definitions. Better-TypeScript depends on TypeScript types for new JavaScript, so it is automatically included if you use Better-TypeScript.
NPM: better-typescript
GitHub: BenjaminAster/Better-TypeScript
Install using npm:
npm i --save better-typescript@latest
Reference the type definitions directly in your TypeScript/JavaScript files...
/// <reference types="better-typescript" />
...or include them in your tsconfig.json
or jsconfig.json
:
{
"compilerOptions": {
"types": ["better-typescript"],
},
}
For worklets, use better-typescript/worklet/<WORKLET_NAME>
as the path:
better-typescript/worklet/audio
for audio workletsbetter-typescript/worklet/paint
for paint workletsbetter-typescript/worklet/layout
for layout workletsbetter-typescript/worklet/animation
for animation worklets
/// <reference types="better-typescript/worklet/audio" />
/// <reference types="better-typescript/worklet/paint" />
/// <reference types="better-typescript/worklet/layout" />
/// <reference types="better-typescript/worklet/animation" />
.querySelector()
element parser (view source)
A querySelector parser that parses the CSS selector and automatically returns the interface for the respective element:
document.querySelector("a#foo.bar") // HTMLAnchorElement
document.querySelector("form.info input[type=radio][name=test]:nth-of-type(even)") // HTMLInputElement
document.querySelector(".math-output mrow ~ munderover[displaystyle=false]") // MathMLElement
document.querySelector("svg#logo > filter:first-of-type feTurbulence:not([type=fractalNoise])") // SVGFETurbulenceElement
element.querySelectorAll(":scope > li:nth-of-type(odd)") // NodeListOf<HTMLLIElement>
If the element type itself cannot be determined, but the element is a descendant of any SVG or MathML element, the element automacially becomes an SVGElement
or MathMLElement
, respectively:
document.querySelector("math.formula .fraction") // MathMLElement
document.querySelector("filter#displacement-filter > .noise") // SVGElement
document.querySelector("body > #logo-svg foreignObject[height] msubsup .power") // MathMLElement
Just to be clear: This parser is not written in TypeScript, it's written solely in TypeScript type definitions (files ending in .d.ts
). This works similarly to HypeScript.
.matches()
(view source)
You can now use element.matches(selector)
and Better-TypeScript will automatically detect the element and provide you with its type definitions when using it in an if-statement.
const element = document.querySelector(".foo");
if (element.matches("img")) {
// `element` has type `HTMLImageElement` in this scope
element.src = "https://bigrat.monster/media/bigrat.png";
} else if (element.matches("dialog[open]")) {
// `element` has type `HTMLDialogElement` in this scope
element.showModal();
} else if (element.matches("body > a#main-link[href]")) {
// `element` has type `HTMLAnchorElement` in this scope
element.href = "https://youtube.be/dQw4w9WgXcQ";
} else if (element.matches<HTMLTextAreaElement>(".inputfield")) {
// `element` has type `HTMLTextAreaElement` in this scope
element.value = "Hello world!";
}
Service workers (view source)
Working with service workers with type checking enabled is an awful experience by default as in TypeScript, there is no ServiceWorker
lib, only a WebWorker
one. Stuff like self.registration
, self.clients
or the fetch
event aren't available by default because from TypeScript's perspecitve, self
alywas has the type WorkerGlobalScope
in workers, not ServiceWorkerGlobalScope
. The way you could previously get around this is by declaring a variable const _self = self as unknown as ServiceWorkerGlobalScope;
and then working with this _self
instead of self
as the global object. This is very ugly and hacky, so Better-TypeScript simply provides all service worker related stuff out of the box to all files.
self.addEventListener("fetch", (event) => {
// `event` has type `FetchEvent`
})
Shared workers (view source)
The same as for service workers also applies to shared workers. You can now use all shared worker related things out of the box.
self.addEventListener("connect", (event) => {
// `event` has type `MessageEvent`
})
Tuple (view source)
This adds the Tuple
type to create fixed length arrays:
// 💩 previos method:
const color: [number, number, number, number] = [255, 0, 0, 255];
// 😎 with Better-TypeScript:
const color: Tuple<number, 4> = [255, 0, 0, 255];
Accept more things (view source)
Many JavaScript functions also accept numbers which then get automatically converted into a string. TypeScript often just accepts strings, so Better-TypeScript adds the ability to call a function with numbers instead of strings.
window.addEventListener("pointermove", (event) => {
document.documentElement.style.setProperty("--mouse-x", event.clientX); // would be an error without Better-TypeScript
document.documentElement.style.setProperty("--mouse-y", event.clientY);
});
const searchParams = new URLSearchParams(location.search);
searchParams.set("count", ++count); // would be an error without Better-TypeScript
history.replaceState(null, "", "?" + searchParams.toString());
Also, for some reason, TypeScript doesn't allow calling history.pushState()
and history.replaceState()
without the unused second parameter. Better-TypeScript adds support for that:
history.replaceState({ someState: 4 });
history.pushState({ someOtherState: 42 });
.cloneNode()
(view source)
When calling .cloneNode()
on any element or fragment, TypeScript just returns the Node
type by default which is not very useful. With Better-TypeScript, .cloneNode()
returns the type of the element that it is called on.
const anchorTemplate = document.createElement("a");
anchorTemplate.href = "https://example.com";
anchorTemplate.textContent = "example";
const anchorClone = anchorTemplate.cloneNode(true); // has the type `HTMLAnchorElement` instead of just `Node`
const fragment = document.querySelector("template#foo-template").content;
for (let i = 0; i < 10; i++) {
const clone = fragment.cloneNode(true); // has the type `DocumentFragment` instead of just `Node`
clone.querySelector("a.destination").href = "https://example.com";
document.body.append(clone);
}
Typed OffscreenCanvas options (view source)
When getting a specific canvas context (e.g. "2d"
, "webgl2"
, ...) from a normal canvas element, TypeScript automatically detects the context type and gives the the second options parameter the right options type.
const canvas = document.querySelector("canvas");
canvas.getContext("2d", { alpha: false }); // options parameter has type `CanvasRenderingContext2DSettings`
However, when using an OffscreenCanvas
, TypeScript does not provide you with these type definitions and gives the options parameter a type of any
which is not very useful. Better-TypeScript adds the right options interfaces.
const canvas = new OffscreenCanvas(width, height);
canvas.getContext("2d", { alpha: false }); // the options parameter now has type `OffscreenCanvasRenderingContext2DSettings`
Non-standard stuff (view source)
This includes various non-standard features that are not part of any specification, e.g. Brave's navigator.brave.isBrave()
, Firefox' CSSMozDocumentRule
or Chromium & WebKit's scrollIntoViewIfNeeded()
function. This can also be used for spoofing-resistant browser detection.
const isBrave = await navigator.brave?.isBrave?.();
if (Element.prototype.scrollIntoViewIfNeeded) element.scrollIntoViewIfNeeded(true);
else element.scrollIntoView({ block: "center" });
const isChromium = Boolean(window.chrome || Intl.v8BreakIterator);
This simply provides the TypedArray
type as a union type alias of all typed arrays (Int8Array
, Uint8Array
, Uint8ClampedArray
, Int16Array
, Uint16Array
, Int32Array
, Uint32Array
, BigInt64Array
, BigUint64Array
, Float32Array
and Float64Array
).
const sendArrayToWASM = (array: TypedArray) => {
const buffer = array.buffer;
// do some magic...
return pointer;
}
Element
extensions (view source)
When an element just has a type of Element
and not HTMLElement
/SVGElement
/MathMLElement
, properties such as .dataset
or .style
are not available. Better-TypeScript adds them.
for (const child of document.body.children) {
child.dataset.foo = "bar"; // error without Better-TypeScript because `child` has type `Element`
}
TreeWalker filters (view source)
When using a SHOW_ELEMENT
, SHOW_TEXT
or SHOW_COMMENT
filter when creating a tree walker, the tree walker just walks over elements, text nodes and comments, respectively. Better-TypeScript provides intelligent typings for these.
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
while (walker.nextNode()) {
const currentNode = walker.currentNode; // has type `Text`
}
Callable WebAssembly export functions (view source)
A WebAssembly export can either be a function, a WebAssembly.Global
, a WebAssembly.Memory
or a WebAssembly.Table
. Because TypeScript doesn't know which export has which type, you ironically cannot do anything on an export, not even call it as a function. With Better-TypeScript, an export is function, a WebAssembly.Global
, a WebAssembly.Memory
and a WebAssembly.Table
all at the same time, which allows you to finally call function exports.
const { module, instance } = await WebAssembly.instantiateStreaming(await window.fetch("./test.wasm"));
const result = instance.exports.add(34, 35); // error without Better-TypeScript
DOMParser
returns right document type (view source)
Calling DOMParser.prototype.parseFromString()
returns the generic Document
interface in pure TypeScript. With Better-TypeScript, it returns Document
for "text/html"
and XMLDocument
for "application/xhtml+xml"
, "application/xml"
, "image/svg+xml"
and "text/xml"
.
const doc = new DOMParser().parseFromString(svg, "image/svg+xml"); // has type `XMLDocument`
Bubbling ShadowRoot
events (view source)
Events of DOM elements inside a ShadowRoot
bubble and can be listened for by a ShadowRoot
. Better-TypeScript adds support for these events.
customElements.define("my-element", class extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.append(template.content.cloneNode(true));
this.shadowRoot.addEventListener("pointerdown", (event) => {
console.log(event.pointerType); // `event` has type `PointerEvent`
});
}
});
Performance entry types (view source)
When calling navigator.performance.getEntriesByType
, Better-TypeScript automatically returns an array of the corresponding performance entry type instead of just the generic PerformanceEntry
.
const siteLoadingType = performance.getEntriesByType("navigation")[0]?.type; // error without Bettery-TypeScript
DevTools Custom Object Formatters (view source)
Chromium & Firefox DevTools support a feature called Custom Object Formatters that let you customize the appearance and interactivity of objects printed in the JavaScript console (To enable this feature, click Enable custom formatters
in the DevTools settings of Chromium or Firefox). Better-TypeScript adds type definitions for this feature.
const recursion = {};
const style = "border: 1px solid red; inline-size: fit-content";
window.devtoolsFormatters = [{
header: (object) => object === recursion ? ["span", { style }, "Hello"] : null,
hasBody: (object) => object === recursion,
body: (object) => ["ol", {}, ["li", {}, "world!"], ["li", {}, ["object", { object }]]],
}];
console.log(recursion);
Event target for HTMLFieldSetElement
(view source)
Event.prototype.target
is always just EventTarget
by default, which is not very helpful. Better-TypeScript adds support for Event.prototype.target
and Event.prototype.currentTarget
for HTMLInputElement
, HTMLSelectElement
, HTMLSelectMenuElement
and HTMLTextAreaElement
(always the element itself) as well as HTMLFieldSetElement
(a union type of HTMLInputElement
, HTMLSelectElement
, HTMLSelectMenuElement
and HTMLTextAreaElement
). This allows you to group multiple form elements (such as <input type="radio" />
) in one <fieldset>
and listen to a change with only a single event listener.
<fieldset id="favorite-animal">
<legend>Choose your favorite animal</legend>
<label>
<input type="radio" name="favorite-animal" value="cat" />
Cat
</label>
<label>
<input type="radio" name="favorite-animal" value="dog" />
Dog
</label>
<label>
<input type="radio" name="favorite-animal" value="psychrolutes-marcidus" checked />
Psychrolutes marcidus
</label>
</fieldset>
let favoriteAnimal: string;
document.querySelector("fieldset#favorite-animal").addEventListener("change", ({ target }) => {
favoriteAnimal = target.value;
});
InputEvent
interface for "input"
event types (view source)
When the browser fires an "input"
event, the event usually is an InputEvent
. But because there are situations where it just has the generic Event
type (e.g. with an <input type="range" />
), TypeScript always gives "input"
events the Event
interface as its type, which makes working with it very impractical. Better-TypeScript reverts this and makes "input"
events always have the InputEvent
type so that you can use its properties (specifically data
, dataTransfer
, inputType
, isComposing
and getTargetRanges()
).
element.addEventListener("input", (event) => {
// `event` now has type `InputEvent`
console.log(event.inputType);
});
execCommand
command id enum (view source)
Better-TypeScript adds enum values for the commandId
parameter of document.execCommand()
so that your editor can autocomplete the string. Keep in mind that the behavior of execCommand()
is very inconsistent between browsers and there is only an unofficial draft specification. MDN even goes as far as marking the function as "deprecated".
Custom element classes (view source)
Better-TypeScript adds type checking for the static attributes and lifecycle callback functions of custom element classes.
customElements.define("my-element", class extends HTMLElement {
constructor() {
super();
};
connectedCallback() { };
disconnectedCallback() { };
adoptedCallback(oldDocument: Document, newDocument: Document) { };
attributeChangedCallback(name: string, oldValue: string, newValue: string, namespace: string) { };
formAssociatedCallback(form: HTMLFormElement) { };
formDisabledCallback(disabled: boolean) { };
formResetCallback() { };
formStateRestoreCallback(newState: any, mode: string) { };
static observedAttributes: string[] = [];
static disabledFeatures: string[] = [];
static formAssociated: boolean = true;
});
Callback functions (view source)
[TODO]: add description
ECMAScript stuff (view source)
For some reason, TypeScript's standard library thinks that Object.getOwnPropertyDescriptors()
returns just strings as its object keys. Better-TypeScript adds support for symbol keys.
const logOwnKeys = (object: any) => {
const descriptors = Object.getOwnPropertyDescriptors(object);
for (const key of Reflect.ownKeys(descriptors)) {
console.log(key, descriptors[key]); // error without Better-TypeScript
}
};
[TODO]: add description for Array::includes & Function::{call, apply, bind}
[TODO]: add description