Skip to content

TypeScript

Introduction

What is this?

Knuckles (the toolkit) offers an official plugin for the analyzer, enabling type-checking of bindings in Knockout views using TypeScript. This helps identify type errors and undeclared properties in your views.

html
<p data-bind="text: myNumber"></p>
                    ~~~~~~~~
Type 'number' is not assignable to parameter of type 'string'.

How does it work?

By providing a type declaration of the viewmodel and adding a hint for its path, the analyzer recognizes the properties defined in the viewmodel. If you use undeclared properties or pass incorrect types to binding handlers, the analyzer will flag an issue.

html
<!-- ok with: default from "./path/to/viewmodel" -->
  <p data-bind="text: myTex"></p>
                      ~~~~~
  Cannot find name 'myTex'. Did you mean 'myText'?
<!-- /ok -->

See more about how to importing view models.

Setup

sh
# Make sure you have the CLI installed.
$ npm install --save-dev @knuckles/cli

# This will configure the analyzer and typescript for you.
$ npx ko add typescript
sh
# Make sure you have the CLI installed.
$ yarn add --dev @knuckles/cli

# This will configure the analyzer and typescript for you.
$ yarn ko add typescript
sh
# Make sure you have the CLI installed.
$ pnpm add --save-dev @knuckles/cli

# This will configure the analyzer and typescript for you.
$ pnpm ko add typescript
sh
# Make sure you have the CLI installed.
$ bun add --save-dev @knuckles/cli

# This will configure the analyzer and typescript for you.
$ bun ko add typescript

Usage

Importing View Models

Knuckles (the toolkit) provides an universal syntax for providing additional insights to the view called hints. Notice the below comment starts with "ok" instead of "ko".

The "with" hint is used to provide view models. Look at the below examples for details. The imported view model can be a factory (class or function) or a singleton object. You can also configure custom interop for other solutions.

html
<!-- ok with: ... -->
  ...
<!-- /ok -->

Examples:

Ex: default import

View Model:

ts
export default ViewModel;

View:

html
<!-- ok with: default from "./path/to/viewmodel" -->
  ...
<!-- /ok -->
Ex: named import

View Model:

ts
export { ViewModel };

View:

html
<!-- ok with: { ViewModel } from "./path/to/viewmodel" -->
  ...
<!-- /ok -->
Ex: '*' import (CJS)

View Model:

ts
export = ViewModel;

View:

html
<!-- ok with: * from "./path/to/viewmodel" -->
  ...
<!-- /ok -->
Ex: type-only import
html
<!-- ok with: type default from "./path/to/viewmodel" -->
  ...
<!-- /ok -->

Declaring Custom Binding Handlers

WARNING

If you declare the binding in a module (a file with exports), you need to wrap the declaration in a declare global block. See TypeScript official documentation about declaration merging.

ts
ko.bindingHandlers.customHandler = ...;

namespace Knuckles { 
  export interface Bindings { 
    customHandler: Binding< 
      /* input value */, 
      /* allowed nodes */
    >; 
  } 
} 

Examples:

Ex: simple binding
ts
namespace Knuckles {
  export interface Bindings {
    text: Binding<string>;
  }
}
Ex: restricting allowed nodes
ts
namespace Knuckles {
  export interface Bindings {
    value: Binding<string, HTMLInputElement>;
  }
}
Ex: custom child context
ts
namespace Knuckles {
  export interface Bindings {
    with: <T extends object, C extends Ctx>(
      n: Comment | Element,
      v: T | ko.Observable<T>,
      c: C,
    ) => Overwrite<
      C,
      {
        $parentContext: C;
        $parents: [C["$data"], ...C["$parents"]];
        $parent: C["$data"];
        $root: C["$root"];
        $data: ko.Unwrapped<T>;
        $rawData: T;
      }
    >;
  }
}

Custom Module Interop

You can configure the algrothim applied to the typing declaration of imported view models.

ts
namespace Knuckles {
  export interface Settings {
    interop: 'custom';
  }

  export type CustomInterop<T> = ...;
}