Frontend Development 14 min read

Building a TypeScript Utility Library: setDefaults, getProperty, Testing and Multi‑Format Packaging

This article explains how to design and implement a TypeScript utility package with complex type declarations for functions like setDefaults and getProperty, demonstrates unit testing using ts‑mocha and chai, and details the compilation and packaging process for CommonJS, ES modules, type declarations, and UMD bundles using Rollup.

Qunar Tech Salon
Qunar Tech Salon
Qunar Tech Salon
Building a TypeScript Utility Library: setDefaults, getProperty, Testing and Multi‑Format Packaging

Author introduction: Zhai Xuguang, who joined Qunar's ticket front‑end team in 2019, focuses on engineering efficiency, TypeScript, and exploring source code.

Technical points covered include complex type declarations for utility functions, unit testing with ts‑mocha and chai , and building the library for multiple module formats using Rollup.

setDefaults is a function that recursively merges one or more default objects into a target object. It supports a customizer callback, overloads for different numbers of source objects, and uses TypeScript overload signatures to provide precise type inference.

function setDefaults(obj: any, ...defaultObjs: any[]): any { /* implementation */ }

The implementation first copies the array of default objects, extracts a possible customizer function, and then reduces the defaults by calling assignObjectDeep to merge each default into the target, handling deep objects and custom logic.

getProperty retrieves a nested property from an object using a string path or an array of keys, with an optional default value. It overloads for different path lengths and supports objects, arrays, and primitive values, falling back to the default when the resolved value is undefined or null .

function getProperty(object: any, path: PropertyPath, defaultValue?: any): any { /* implementation */ }

Testing is performed with ts‑mocha and chai . Test cases cover invalid objects, correct property retrieval for both dot‑notation and bracket‑notation paths, handling of wrong paths, and array indexing.

describe('getProperty', () => { /* test cases */ });

The library is compiled into CommonJS, ES module, and UMD formats. The base tsconfig.json defines strict compiler options, and separate configs extend it to set the module type and output directory for each format.

{
  "compilerOptions": {
    "noImplicitAny": true,
    "removeComments": true,
    "preserveConstEnums": false,
    "allowSyntheticDefaultImports": true,
    "sourceMap": false,
    "types": ["node", "mocha"],
    "lib": ["es5"],
    "downlevelIteration": true
  },
  "include": ["./src/**/*.ts"]
}

ESM and CJS configs inherit from the base config and override the module and outDir fields. The Types config enables declaration generation and directs the output to ./dist/types .

Rollup is used for the UMD bundle. Plugins include @rollup/plugin-commonjs , @rollup/plugin-node-resolve , @rollup/plugin-typescript , rollup-plugin-replace , and rollup-plugin-terser for production minification. Peer dependencies are marked as external, and the global name is set to FlightCommonUtils .

import nodeResolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import replace from 'rollup-plugin-replace';
import { terser } from 'rollup-plugin-terser';
import pkg from './package.json';

const env = process.env.NODE_ENV;

const config = {
  input: 'src/index.ts',
  output: { format: 'umd', name: 'FlightCommonUtils' },
  external: Object.keys(pkg.peerDependencies || {}),
  plugins: [
    commonjs(),
    nodeResolve({ jsnext: true }),
    typescript({ tsconfig: './tsconfig.esm.rollup.json' }),
    replace({ 'process.env.NODE_ENV': JSON.stringify(env) })
  ]
};

if (env === 'production') {
  config.plugins.push(
    terser({ compress: { pure_getters: true, unsafe: true, unsafe_comps: true, warnings: false } })
  );
}

export default config;

Package scripts automate the build steps: cleaning output directories, compiling CJS, ESM, and type declarations, and generating both development and production UMD bundles with Rollup.

The final package.json declares main (CJS), module (ESM), types (type declarations), and umd (UMD bundle) fields, ensuring proper consumption by Node, bundlers, and TypeScript projects.

In summary, the article walks through the motivation, design, complex TypeScript typings, testing strategy, and full build pipeline for a reusable front‑end utility library, highlighting the challenges of accurate type definitions and multi‑format packaging.

frontendTypeScriptTestingbuild toolsrolluputility functionsts-mocha
Qunar Tech Salon
Written by

Qunar Tech Salon

Qunar Tech Salon is a learning and exchange platform for Qunar engineers and industry peers. We share cutting-edge technology trends and topics, providing a free platform for mid-to-senior technical professionals to exchange and learn.

0 followers
Reader feedback

How this landed with the community

login 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.