UNPKG

build-url-ts

Version:

A small library that builds a URL given its components

560 lines (445 loc) β€’ 13.2 kB
# build-url-ts [![Build Status](https://github.com/meabed/build-url-ts/actions/workflows/ci.yml/badge.svg)](https://github.com/meabed/build-url-ts/actions/workflows/ci.yml) [![NPM version](https://img.shields.io/npm/v/build-url-ts.svg)](https://www.npmjs.com/package/build-url-ts) [![Downloads](https://img.shields.io/npm/dm/build-url-ts.svg)](https://www.npmjs.com/package/build-url-ts) [![Bundle Size](https://img.shields.io/bundlephobia/minzip/build-url-ts)](https://bundlephobia.com/package/build-url-ts) [![UNPKG](https://img.shields.io/badge/UNPKG-OK-179BD7.svg)](https://unpkg.com/browse/build-url-ts@latest/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) A small, fast, zero-dependency library for building URLs with a fluent API. Fully typed for TypeScript and runs anywhere JavaScript does β€” Node.js, Bun, Deno, edge runtimes, and all modern browsers. [![Edit build-url-ts-demo](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/build-url-ts-demo-qer8y?fontsize=14&hidenavigation=1&theme=dark) ## Features - πŸš€ **Small & Fast** - Minimal footprint with zero dependencies (~1.3 KB min+gzip) - πŸ“¦ **TypeScript Support** - Full TypeScript definitions for both ESM and CJS - πŸ”€ **Dual Package** - First-class ESM + CommonJS entry points, plus a UMD bundle for CDNs - 🌳 **Tree-shakeable** - Side-effect-free ESM; import only what you use - 🌐 **Universal** - Node.js, Bun, Deno, edge runtimes, and all modern browsers - πŸ”§ **Flexible** - Multiple ways to handle array query parameters - ✨ **Clean API** - Simple and intuitive interface - πŸ›‘οΈ **Safe** - Properly encodes URLs and handles edge cases - πŸ§ͺ **Well Tested** - Comprehensive test coverage with 137+ test cases - πŸ”„ **Smart Merging** - Automatically merges with existing query parameters ## Installation ```bash # bun bun add build-url-ts # npm npm install build-url-ts # yarn yarn add build-url-ts # pnpm pnpm add build-url-ts ``` ## Quick Start ```typescript import { buildUrl } from 'build-url-ts'; const url = buildUrl('https://api.example.com', { path: 'users/123', queryParams: { tab: 'profile', limit: 10 } }); // Result: https://api.example.com/users/123?tab=profile&limit=10 ``` ## Module Formats & Platforms The package ships ESM, CommonJS, and a minified UMD bundle, with separate type declarations for each module system, so it works the same everywhere. ### ESM β€” bundlers, Node, Bun ```typescript import { buildUrl } from 'build-url-ts'; ``` ### CommonJS β€” `require()` ```javascript const { buildUrl } = require('build-url-ts'); ``` ### Deno ```typescript import { buildUrl } from 'npm:build-url-ts'; ``` ### Browser via CDN (`<script>`) The UMD build exposes a global `buildUrl`: ```html <script src="https://unpkg.com/build-url-ts"></script> <script> buildUrl.buildUrl('https://example.com', { path: 'about' }); </script> ``` > For production, pin a version and add Subresource Integrity, e.g. > `<script src="https://unpkg.com/build-url-ts@6.2.0/dist/index.umd.min.js" integrity="sha384-…" crossorigin="anonymous"></script>`. Or as an ES module, no bundler required: ```html <script type="module"> import { buildUrl } from 'https://esm.sh/build-url-ts'; console.log(buildUrl('https://example.com', { path: 'about' })); </script> ``` ### Tree-shaking The library is published as side-effect-free ESM (`"sideEffects": false`), so bundlers drop everything you don't import. Pull in a single helper and only that helper ends up in your bundle: ```typescript import { buildQueryString } from 'build-url-ts'; // buildUrl, appendPath, buildHash are tree-shaken away ``` ## API Reference ### `buildUrl(baseUrl?, options?)` Builds a complete URL from components. #### Parameters - `baseUrl` (optional): `string | null` - The base URL - `options` (optional): `IBuildUrlOptions` - URL building options #### Options ```typescript interface IBuildUrlOptions { path?: string | number; // Single path segment paths?: (string | number)[]; // Multiple path segments, appended in order queryParams?: IQueryParams; // Query parameters object hash?: string | number; // Hash/fragment identifier lowerCase?: boolean; // Convert to lowercase disableCSV?: boolean | IDisableCsvType; // Array handling } ``` ## Usage Examples ### Basic URL Building ```typescript import { buildUrl } from 'build-url-ts'; // Simple URL with path buildUrl('https://example.com', { path: 'about' }); // β†’ https://example.com/about // Multiple path segments (normalized and joined); `path` still works for one buildUrl('https://example.com', { paths: ['about', '/my/', '/cat'] }); // β†’ https://example.com/about/my/cat // With query parameters buildUrl('https://example.com', { path: 'search', queryParams: { q: 'typescript', category: 'tutorials' } }); // β†’ https://example.com/search?q=typescript&category=tutorials // With hash buildUrl('https://example.com', { path: 'docs', hash: 'installation' }); // β†’ https://example.com/docs#installation // All combined buildUrl('https://api.example.com', { path: 'v1/users', queryParams: { role: 'admin', active: true }, hash: 'summary' }); // β†’ https://api.example.com/v1/users?role=admin&active=true#summary ``` ### Working with Arrays ```typescript // Default: Arrays as comma-separated values buildUrl('https://api.example.com', { queryParams: { ids: [1, 2, 3] } }); // β†’ https://api.example.com?ids=1,2,3 // Arrays as repeated parameters buildUrl('https://api.example.com', { queryParams: { id: [1, 2, 3] }, disableCSV: true }); // β†’ https://api.example.com?id=1&id=2&id=3 // Arrays with bracket notation buildUrl('https://api.example.com', { queryParams: { id: [1, 2, 3] }, disableCSV: 'array' }); // β†’ https://api.example.com?id[]=1&id[]=2&id[]=3 // Arrays with indexed notation (ascending) buildUrl('https://api.example.com', { queryParams: { id: [1, 2, 3] }, disableCSV: 'order_asc' }); // β†’ https://api.example.com?id[0]=1&id[1]=2&id[2]=3 // Arrays with indexed notation (descending) buildUrl('https://api.example.com', { queryParams: { id: [1, 2, 3] }, disableCSV: 'order_desc' }); // β†’ https://api.example.com?id[2]=1&id[1]=2&id[0]=3 ``` ### Case Transformation ```typescript // Convert to lowercase buildUrl('https://example.com', { path: 'About', hash: 'Contact', queryParams: { Filter: 'NEW' }, lowerCase: true }); // β†’ https://example.com/about?filter=new#contact ``` ### Building Partial URLs ```typescript // Query string only buildUrl(null, { queryParams: { page: 1, limit: 20 } }); // β†’ ?page=1&limit=20 // Path only buildUrl(null, { path: 'users/profile' }); // β†’ /users/profile // Hash only buildUrl(null, { hash: 'top' }); // β†’ #top // Using options as first parameter buildUrl({ path: 'api/v2', queryParams: { format: 'json' } }); // β†’ /api/v2?format=json ``` ### Handling Special Values ```typescript // Null values become empty strings buildUrl('https://api.example.com', { queryParams: { name: 'John', age: null } }); // β†’ https://api.example.com?name=John&age= // Undefined values are omitted buildUrl('https://api.example.com', { queryParams: { name: 'John', age: undefined } }); // β†’ https://api.example.com?name=John // Number values buildUrl('https://api.example.com', { path: 404, queryParams: { code: 0, retry: 3 } }); // β†’ https://api.example.com/404?code=0&retry=3 // Boolean values buildUrl('https://api.example.com', { queryParams: { active: true, deleted: false } }); // β†’ https://api.example.com?active=true&deleted=false // Date objects const date = new Date('2024-01-01T00:00:00Z'); buildUrl('https://api.example.com', { queryParams: { created: date } }); // β†’ https://api.example.com?created=Mon%20Jan%2001%202024... // Nested objects (automatically stringified) buildUrl('https://api.example.com', { queryParams: { filter: { status: 'active', role: 'admin' } } }); // β†’ https://api.example.com?filter=%7B%22status%22%3A%22active%22%2C%22role%22%3A%22admin%22%7D ``` ## Advanced Usage ### Using Individual Functions The library also exports individual functions for more granular control: ```typescript import { buildQueryString, appendPath, buildHash } from 'build-url-ts'; // Build query string only const qs = buildQueryString({ search: 'typescript', limit: 10 }); // β†’ ?search=typescript&limit=10 // Append path to URL const urlWithPath = appendPath('users/123', 'https://api.example.com'); // β†’ https://api.example.com/users/123 // Build hash fragment const hash = buildHash('section-2'); // β†’ #section-2 ``` ### TypeScript Types ```typescript import type { IQueryParams, IBuildUrlOptions, IDisableCsvType } from 'build-url-ts'; // Custom query params type interface MyParams extends IQueryParams { userId: number; tags?: string[]; active?: boolean; } const options: IBuildUrlOptions = { path: 'api/users', queryParams: { userId: 123, tags: ['admin', 'verified'] } as MyParams }; ``` ## URL Encoding All values are properly encoded for URLs: ```typescript buildUrl('https://example.com', { queryParams: { name: 'John Doe', email: 'john@example.com', message: 'Hello & goodbye!', unicode: 'δ½ ε₯½δΈ–η•Œ' } }); // β†’ https://example.com?name=John%20Doe&email=john%40example.com&message=Hello%20%26%20goodbye!&unicode=%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C ``` ## Edge Cases The library handles various edge cases gracefully: ```typescript // Empty or missing base URL buildUrl('', { path: 'api' }); // β†’ /api buildUrl(null, { path: 'api' }); // β†’ /api buildUrl(undefined, { path: 'api' }); // β†’ /api // Trailing slashes buildUrl('https://example.com/', { path: '/users' }); // β†’ https://example.com/users (no double slash) // Empty values buildUrl('https://example.com', { path: '', // ignored hash: '', // ignored queryParams: { empty: '', // included as empty zero: 0, // included as "0" false: false // included as "false" } }); // β†’ https://example.com?empty=&zero=0&false=false // URLs with existing query parameters (automatic merging) buildUrl('https://example.com?existing=param', { queryParams: { new: 'value' } }); // β†’ https://example.com?existing=param&new=value // URLs with ports and authentication buildUrl('http://user:pass@localhost:3000', { path: 'api/secure' }); // β†’ http://user:pass@localhost:3000/api/secure // Special protocols buildUrl('file:///home/user/data', { queryParams: { version: 2 } }); // β†’ file:///home/user/data?version=2 // Internationalized domain names and emoji buildUrl('https://δΎ‹γˆ.jp', { queryParams: { search: 'πŸ”', text: 'δ½ ε₯½' } }); // β†’ https://δΎ‹γˆ.jp?search=%F0%9F%94%8D&text=%E4%BD%A0%E5%A5%BD // Empty arrays are omitted buildUrl('https://api.example.com', { queryParams: { ids: [], name: 'test' } }); // β†’ https://api.example.com?name=test // Arrays with null/undefined values buildUrl('https://api.example.com', { queryParams: { items: ['one', null, undefined, 'four'] }, disableCSV: true }); // β†’ https://api.example.com?items=one&items=&items=four // (undefined values are filtered out) ``` ## Migration Guide ### From `build-url` This library is a TypeScript fork of the original `build-url` package with improvements: ```javascript // Before (build-url) var buildUrl = require('build-url'); // After (build-url-ts) import { buildUrl } from 'build-url-ts'; ``` The API remains fully compatible, so you can simply replace the import. ## Contributing Contributions are welcome! Please feel free to submit a Pull Request. 1. Fork the repository 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 3. Commit your changes (`git commit -m 'Add some amazing feature'`) 4. Push to the branch (`git push origin feature/amazing-feature`) 5. Open a Pull Request ## Development This project uses [Bun](https://bun.sh) as its package manager, test runner, and script runner. ```bash # Install dependencies bun install # Run tests bun test # Run tests in watch mode bun run test:watch # Run tests with coverage bun run test:coverage # Build the library (CJS + ESM + type declarations) bun run build # Lint and format (Biome) bun run lint bun run check # Type checking bun run typecheck ``` ### Test Coverage The library has comprehensive test coverage with 137+ test cases covering: - Basic URL building scenarios - Various array handling modes - Special characters and encoding - Edge cases and error handling - Protocol support (http, https, file, ftp, etc.) - Internationalization and emoji support - Query parameter merging - Date and object serialization ## License This is licensed under MIT License. [See details](LICENSE) ## Acknowledgments This is a TypeScript enhancement of the original [build-url](https://github.com/steverydz/build-url) library by [Steve Rydz](https://github.com/steverydz).