Refactoring a Hybrid App SDK from JavaScript to TypeScript with Type Definitions and Build Configuration

This article describes how to refactor a large JavaScript hybrid‑app SDK into TypeScript, set up Rollup and Babel for building, create proper .d.ts type definitions, use generics, overloads, and type guards, and solve path‑alias issues for reliable IDE support.

Fulu Network R&D Team
Fulu Network R&D Team
Fulu Network R&D Team
Refactoring a Hybrid App SDK from JavaScript to TypeScript with Type Definitions and Build Configuration

Background – The original hybrid‑app SDK was written in JavaScript with separate documentation for API methods, which provided no IDE type hints. Maintaining a parallel type definition package caused synchronization problems, and the single file grew to nearly a thousand lines, prompting a rewrite in TypeScript.

Preparation – Because the library is pure JavaScript, Rollup + Babel is used for bundling. A simple project structure is introduced, including babel.config.js, package.json, README.md, rollup.config.js, tsconfig.build.json, tsconfig.json, typings.d.ts, a dist folder for output, and a src folder containing global.d.ts, index.ts, an api sub‑folder, a lib/sdk.ts file, and a utils folder.

│  babel.config.js
│  package.json
│  README.md
│  rollup.config.js
│  tsconfig.build.json
│  tsconfig.json
│  typings.d.ts
├─dist  // final output
└─src
    │  global.d.ts
    │  index.ts // entry point exposing variables and API methods
    ├─api // API methods
    │  ├─media
    │  ├─tool
    │  └─ui
    ├─lib
    │      sdk.ts   // SDK constructor
    └─utils // utility functions

Declaring Callback Functions – Generics are used so that the invoke method returns a Promise of any type passed in. For example, global.user.get can return an object with user_id and is_admin properties.

Modifying Native Type Variables – New properties are added to the window object, which initially triggers TypeScript errors because the built‑in lib.dom.d.ts does not contain them. The solution is to create a global global.d.ts module that merges with the existing Window interface, adding the required members without polluting the original definitions.

When extending a promise with an abort method, a custom AbortablePromise interface is defined to avoid contaminating the base Promise type:

interface AbortablePromise<T extends {}> extends Promise<T> {
  abort: () => void;
}

const invoke = <T extends {}>(method: string): AbortablePromise<T> => {
  let promise = new Promise(resolve => {
    window.WebViewJavascriptBridge.registerHandler(method, (res: T) => resolve(res));
  }) as AbortablePromise<T>;

  promise.abort = () => {
    window.WebViewJavascriptBridge.registerHandler('media.upload.abort');
  };

  return promise;
};

Function overloads are employed to return different promise types based on the method string:

function invoke<T extends {}>(method: 'media.file.upload'): AbortablePromise<T>;
function invoke<T extends {}>(method: Method): Promise<T>;

const x1 = invoke('media.file.upload'); // → AbortablePromise
const x2 = invoke('global.user.get');   // → Promise

Using the is Keyword for Type Guards – Utility functions such as isString are written to narrow any to a specific type, enabling safe property access after a runtime check.

export function isString(arg: any): arg is string {
  return Object.prototype.toString.call(arg) === '[object String]';
}

let a: any = 2;
if (isString(a)) {
  // a is now typed as string
  a.join(); // error: string has no join method
}

Encountered Issue: Alias Path Not Resolved in Generated .d.ts – Using @ as an alias for ./src works during development but the compiled .d.ts still contains the alias. The @zerollup/ts-transform-paths plugin together with ttypescript (run via ttsc) rewrites the paths correctly.

"build:types": "ttsc -p tsconfig.build.json"

After rebuilding, the alias is correctly resolved in the output.

Other Practical Scenarios – In application development, a dedicated types folder can hold declaration files (e.g., ass.d.ts) using declare namespace to define shared interfaces such as pagination parameters. These types are then imported in service files to provide strong typing for API calls.

// api/types/ass.d.ts
declare namespace ass {
  interface PageParams {
    page: number;
    size: number;
  }

  interface CheckinRuleSearchProps {
    /** Rule type */
    rule_type?: string;
    // ...
  }

  interface CheckinRuleListBody extends PageParams, CheckinRuleSearchProps {}
}

// api/ass.ts
export const getLocalCheckinRuleList = (data: ass.CheckinRuleListBody) =>
  post<ass.CheckinRuleListResponse>('/api/v2.0/rule/search', { data });

Summary – Refactoring the SDK to TypeScript provides immediate IDE assistance, automatic type checking, and early bug detection, greatly improving maintainability. The trade‑off is higher initial development effort, especially for fast‑iteration projects where lightweight documentation might be preferred.

References – https://jkchao.github.io/typescript-book-chinese/

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

FrontendSDKTypeScriptGenericsRefactoringtype-guardstype definitions
Fulu Network R&D Team
Written by

Fulu Network R&D Team

Providing technical literature sharing for Fulu Holdings' tech elite, promoting its technologies through experience summaries, technology consolidation, and innovation sharing.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.