--- url: /learn.md description: An initiative to improve performance and modernize the JavaScript ecosystem. --- # Why e18e The e18e project is an initiative to bring together the groups and individuals who are passionate about improving performance of the JavaScript ecosystem. Many ongoing efforts are already happening in this space, from [dependency tree cleanups](https://github.com/e18e/ecosystem-issues) to [performance optimizations](https://marvinh.dev/blog/speeding-up-javascript-ecosystem/), and much more. Our aim is to provide a space for contributions, ideas and knowledge sharing around the importance of performance across the ecosystem. Hopefully, with this collaboration, we can improve visibility of the impact of things like dependency choices. Join us at the [e18e Discord Server](https://chat.e18e.dev) to connect with other like-minded devs. Currently, there are three main areas of focus: * [cleanup](./cleanup.md) - cleaning up dependency trees and modernizing popular tools and libraries across the ecosystem. * [speedup](./speedup.md) - speeding up parts of the ecosystem many of us depend on. * [levelup](./levelup.md) - documenting and providing modern, lighter alternatives to established tools and libraries we all regularly use. --- --- url: /learn/resources.md description: Tools, websites, and resources for improving the JavaScript ecosystem. --- # Resources ## Efforts | | Description | | ---------------------------------------------------------------- | ---------------------------------------------------------------------- | | [es-tooling](https://github.com/es-tooling/) | A collection of tooling for the JS ecosystem | | [JS ecosystem cleanup](https://github.com/e18e/ecosystem-issues) | Efforts to provide a cleaner dependency tree in the JS ecosystem | | [Tinylibs](https://github.com/tinylibs) | A place for tiny and minimal libraries | | [unjs](https://github.com/unjs/) | Purpose-built, high-quality JavaScript utilities, libraries, and tools | ## Tools | | Description | | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | | [arethetypeswrong](https://arethetypeswrong.github.io/) | Analyzes npm packages for issues with TypeScript types, particularly ESM-related module resolution problems | | [Biome's noBarrelFile](https://biomejs.dev/linter/rules/no-barrel-file/) | Biome plugin to avoid barrel files | | [Biome's noReExportAll](https://biomejs.dev/linter/rules/no-re-export-all/) | Biome plugin to avoid re-export all | | [Biome's noRestrictedDependencies](https://biomejs.dev/linter/rules/no-restricted-dependencies/) | Biome rule to suggest modern alternatives for outdated or heavy dependencies | | [@e18e/eslint-plugin](https://github.com/e18e/eslint-plugin) | Official ESLint plugin for modernizing code and suggesting dependency alternatives | | [eslint-plugin-barrel-files](https://github.com/thepassle/eslint-plugin-barrel-files) | ESLint plugin to avoid barrel files | | [howfat](https://github.com/megahertz/howfat) | Shows how much space a package takes up on disk including dependencies | | [Knip](https://knip.dev) | Finds and fixes unused files, dependencies and exports in JavaScript and TypeScript projects | | [Package Size Calculator](https://github.com/TheDevMinerTV/package-size-calculator) | CLI to calculate the size of a package after removing/replacing dependencies, or calculating the difference of two versions of the same package | | [publint](https://publint.dev/) | Lints npm packages to ensure wide compatibility across environments like Vite, Webpack, Rollup, and Node.js | | [unplugin-purge-polyfills](https://github.com/danielroe/unplugin-purge-polyfills) | Unplugin to replace package imports with native code | ## Websites | | Description | | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | [bundlejs](https://bundlejs.com/) | In-browser bundler that shows package size with tree-shaking support, handles CSS and external files, and works with multiple CDNs | | [bundlephobia](https://bundlephobia.com/) | Finds the cost of adding npm packages to your bundle | | [DepTree](https://deptree.rschristian.dev/) | Visualizes a project's dependencies and highlights which of those dependencies could be replaced | | [Ecosyste.ms](https://packages.ecosyste.ms/) | An open API service providing package, version and dependency metadata of many open source software ecosystems and registries | | [node-modules.dev](https://node-modules.dev) | Online tool to visualize your node\_modules, inspect dependencies, and analyze package structures with WebContainer support | | [npmgraph](https://npmgraph.js.org/) | Visually displays a graph of a project's dependencies to get an idea of a projects module graph | | [packagephobia](https://packagephobia.com/) | Reports the "publish size" and "install size" of npm packages before installation, helping you evaluate disk space impact | | [pkg-size.dev](https://pkg-size.dev/) | Explores npm packages with fresh installs to show actual size, dependencies, and helps optimize your node\_modules | | [snyk](https://snyk.io/) | Finds and fixes vulnerabilities in open source dependencies | | [socket.dev](https://socket.dev/) | Security-focused tool that analyzes dependencies for vulnerabilities | | [npmx.dev](https://npmx.dev/) | A better browser for the npm registry with e18e recommendations and advice | ## Learning * [Marvin's Speeding Up the JS Ecosystem series](https://marvinh.dev/blog/speeding-up-javascript-ecosystem/) * [Practical barrel file guide for library authors](https://thepassle.netlify.app/blog/practical-barrel-file-guide-for-library-authors) --- --- url: /learn/advocacy.md description: >- Best practices for contributing e18e performance improvements to open source projects. --- # Advocacy Much of e18e's work involves making changes in existing open source repositories. These repositories generally have their own workflows that their maintainers expect to adhere to. It's important when suggesting changes to repositories to respect those workflows and actively work with those maintainers. ## Coordinating Issues Many repositories prefer discussing potential changes in an issue before they can fully review a pull request. Unless a repository maintainer has explicitly indicated a willingness to bypass issue practices, stick with their normal flow and fully fill out the most applicable issue template if they prefer an issue to be filed ahead of any PRs. [Explaining Changes](#explaining-changes) covers the common information you'll want to include. ### Umbrella e18e Tracking When many changes are possible for a single repository, please search for an "umbrella" issue in [e18e/ecosystem-issues](https://github.com/e18e/ecosystem-issues), and open one if it doesn't already exist. That will help with tracking progress and raising visibility for other e18e participants. We also use these issues to keep track of maintainer preferences around the initiative. If a maintainer requests a preference, especially one to not be sent e18e pull requests, please note it there. ## Explaining Changes Some maintainers are not active participants in e18e or have not heard of it. Suggested changes need to include the contextual information that can clearly and quickly explain to a repository maintainer why the change is for the better. In general, it's best to demonstrate *tangible benefits* that are *specific to the project*. That can change depending on what area(s) of improvement your change falls under. ### Bundle Size When a project creates a built output such as bundled JS files, and a cleanup is meant to improve the size of those bundles, show the actual file size difference. This is important because: * Many changes might change the devDependency tree size, but not impact the bundle * Swapping out direct usage of a dependency that is also deeply required might result in a net bigger bundle In the latter case, you might consider prioritizing working on the upstream consumers of that dependency first. ### Dependency tree If a dependency cleanup is meant to reduce the size of a project's dependency tree, show the actual change to the tree. An isolated dependency graph (e.g. via `npmgraph.js.org`) can be useful to give a quick overview of the difference between two dependencies. However, sometimes it is not representative of how the overall project's dependency tree will be affected. This can be for various reasons, such as the project pulling the same dependencies in elsewhere. Showing a project dependency graph before and after can be more beneficial for this reason, and may help you find occurrences of target packages you weren't aware of. See also [Bundle Size](#bundle-size). ### Speed If a cleanup is meant to improve the performance of a project, showing actual performance measurements for the project would be the best option followed by sharing a more general benchmark together with an explanation of why it is relevant such as the benchmarked method being called in a hot code path. Performance-oriented developers generally expect comparisons to include reproducible experiments. Isolated benchmarks of a new dependency can be useful when the comparison is large (i.e. you're comparing against a *much* slower dependency). However, in cases where the gap is small, it is often better to benchmark the project before and after instead of the dependency itself in isolation. Consider using a high-level tool like [hyperfine](https://github.com/sharkdp/hyperfine) to measure end-to-end performance. Many maintainers just prefer a project benchmark too, so always refer to the contribution guide or ask the maintainers what their preference is. Downstream users often have very different usage scenarios and/or don't use some dependencies in hot paths. If your change does noticeably speed something up, be sure to call it out in the issue/PR description. Developers love noticeable performance improvements! ## What Isn't Convincing Most open source maintainers regularly deal with drive-by contributions that ignore their workflows and do a poor job of explaining themselves. Be careful not to make these common mistakes, as that weakens the case for your change and for e18e as a whole. ### Abstract Reports Don't be vague. Indicating that a cleanup could offer bug fixes, improved developer experience, or other features in the abstract is not convincing without any evidence those impact the project. * For bug fixes, be sure to have a reproduction -- much like any other kind of bug report * For developer experience and other features, you'll similarly want to be able to demonstrate why the new way is tangibly better ### Downloads and Stars Non-user-facing metrics such as GitHub Stars and npm download statistics are not generally convincing. Both can easily be gamed and thus may not be seen as reliable metrics. If your goal is to show that a package is well-known and trusted, it's better to demonstrate the factors that would result in those high numbers. Most often that is from showing lists of dependents that include many existing projects, especially other high-profile ones. ### Support Ranges Some cleanups involve restricting project dependency ranges, such as raising a minimum Node.js version from an older version to the current LTS. Stricter dependency or platform support ranges are a drawback to users as it may cause additional difficulty in upgrading the library. You'll want to demonstrate that the tangible benefits that come from them are large enough for the majority of users that they they are worth the trade-off. For example, if you're proposing removing old code, show that the cleanup improves the bundle size, performance, and any other improved metrics. --- --- url: /learn/projects.md description: A list of ongoing and upcoming projects by the e18e community. --- # Ongoing Projects The e18e community is actively working on countless initiatives, both large and small. Here are our current ongoing larger projects which are longer term: ## Ongoing Projects ### [e18e ESLint Plugin](https://github.com/e18e/eslint-plugin) An ESLint plugin that provides rules for modernizing JavaScript and TypeScript codebases. Includes rules for promoting modern syntax, suggesting lighter-weight alternatives to popular libraries, and identifying patterns that can be optimized for better performance. It replaces the standalone [eslint-plugin-depend](https://github.com/es-tooling/eslint-plugin-depend), incorporating it's dependency replacement feature (the `ban-dependencies` rule). ### [e18e CLI](https://github.com/e18e/cli) A command-line tool for scanning your codebase to detect replaceable code and dependencies. Features an auto-fix capability to migrate code to better alternatives automatically. ### [e18e MCP](https://github.com/e18e/mcp) An MCP (Model Context Protocol) server that advises AI agents on legacy code patterns and dependencies that should be replaced with modern alternatives. ### [Module Replacements](https://github.com/e18e/module-replacements) A community-maintained list of recommended replacements for outdated or suboptimal dependencies. Helps developers make informed decisions about modernizing their dependency stack. ### [e18e Web Features Codemods](https://github.com/e18e/web-features-codemods) A collection of codemods for migrating from older JavaScript syntax to newer web platform features and modern syntax patterns. ### [Module Replacements Codemods](https://github.com/es-tooling/module-replacements-codemods) A collection of codemods for automatically migrating to recommended dependency replacements, streamlining the modernization process. ### [Ecosystem Issues](https://github.com/e18e/ecosystem-issues) A centralized issue tracker for coordinating contributions and initiatives across the JavaScript ecosystem. Commonly used for tracking large-scale migrations and modernization efforts. ### [Dependency Diff GitHub Action](https://github.com/e18e/action-dependency-diff) A GitHub Action that provides detailed reporting on dependency changes in pull requests, helping teams review dependency updates effectively. ### [Framework Tracker](https://github.com/e18e/framework-tracker) A project for tracking and comparing the performance and modernity of popular web frameworks. ## Upcoming Projects ### Replacements GitHub Action A GitHub Action that automatically creates pull requests to migrate codebases to newer syntax, modern dependencies, and improved patterns. ## Get Involved Want to contribute to any of these projects? Join our [discord](https://chat.e18e.dev/) and say Hi! --- --- url: /learn/cleanup.md description: Cleaning up dependency trees and modernizing packages across the ecosystem. --- # Cleanup The JavaScript ecosystem has grown massively over the years, in both good and bad ways. We have all the packages and choice we want, but have built up a vast amount of technical debt along the way. Much of this technical debt exists in the form of packages that are redundant, bloated, or no longer actively maintained. The e18e community maintains [e18e/ecosystem-issues](https://github.com/e18e/ecosystem-issues), which aims to drive contributions upstream to popular open-source projects, including: * Modernizing existing packages * Migrating from redundant packages * Migrating from inactive packages * Migrating to lighter and/or faster alternatives ::: info Jump into action! Check out the list of [open opportunities in the ecosystem-issues repo](https://github.com/e18e/ecosystem-issues/issues). After reading this guide, review these issues for ideas to improve popular packages. If you discover other places where we can clean up a dependency tree or improve performance, please open tracking issues in the cleanup repo to provide starting points for others. We can have a greater impact working together. Join the [e18e Discord server](https://chat.e18e.dev) to connect with others, share your wins, or get help along the way! ::: ## Contribution Guide This guide helps you discover and contribute improvements to the ecosystem. We'll explore common discovery techniques and problems to resolve in upstream packages. ### Discovery We're primarily looking for packages that are: * Bloated * No longer maintained * Outdated * Slow * Redundant * Unused A good starting point is to choose the **target package** you want to improve. For example, we could choose to improve [vite](https://github.com/vitejs/vite). Our first step is to *discover* the current state of this package. We can do this with [npmgraph](https://npmgraph.js.org/) and [pkg-size](https://pkg-size.dev/). In this case, we can visualize vite's dependency graph and size here: * [vite dependency graph](https://npmgraph.js.org/?q=vite) * [vite package size](https://pkg-size.dev/vite) This provides a good overview of dependency tree complexity and overall *install size* (amount of data downloaded during installation). #### Rollup bundles In our particular example, vite has relatively few dependencies, but has an install size we can likely reduce (32MB at time of writing). This is because vite bundles many production dependencies rather than pulling them from `node_modules`. For this reason, we need to go further with rollup bundle analysis. We can use the [`rollup-plugin-visualizer`](https://github.com/btd/rollup-plugin-visualizer) plugin to generate a visualization of vite's bundle. This visualization quickly reveals the larger dependencies in the bundle and the import paths leading to them. #### Note on dependency depth Easy wins in the dependency graph often involve packages that pull in large subtrees of dependencies. While this is generally true, some deep dependencies serve important purposes (e.g., supporting older node versions). Consider this before assuming a dependency tree can be removed. ### Improvement After examining the dependency graph, package size, and bundle distribution, we can identify potential improvements. We should look for: * Dependencies that pull in large subtrees of deeper dependencies not used elsewhere * Duplicated dependencies (either multiple versions or multiple packages with similar functionality) * Very large dependencies (by install size) In our vite example, we can see that both `fast-glob` and `glob` are bundled into vite. This means we're bundling two non-trivial packages that essentially perform the same function—a clear opportunity for improvement! #### Create cleanup issues When you identify potential performance improvements, open issues in this repository. Even if you plan to address them yourself, this improves visibility, prevents duplicate work, and enables others to help. If you lack time to implement the changes yourself, someone else might follow up on your findings. Consider attaching a package report using [DevMiner's package-size-calculator](https://github.com/TheDevMinerTV/package-size-calculator) to provide an overview of the package size and your proposed changes. ### Fixes Once we've identified dependencies that introduce performance issues, we can explore different approaches for improvements. #### Large subtrees For these dependencies, first determine if there's a reason we need them instead of alternatives. Common considerations include: * Node `engines` specifying a version low enough that this dependency supports (but alternatives don't) * Absence of known alternatives (an opportunity to create one!) * Necessity of each deep dependency in the subtree (sometimes depth exists for good reasons, like shared modules) If a dependency is replaceable, look for alternatives. Good resources include: * The [module replacements repository](https://github.com/e18e/module-replacements/tree/main/docs/modules) * [tinylibs](https://github.com/tinylibs/) * [unjs](https://github.com/unjs/) If you find a good, leaner alternative, [share it with the community!](https://chat.e18e.dev) #### Duplicated functionality You'll often find dependencies using different versions or entirely separate solutions to achieve the same functionality. For different versions, contribute upstream to update outdated dependencies to the latest version. For separate solutions, evaluate which one performs better and consider standardizing on that solution. In our vite example, we would identify what uses `fast-glob` and what uses `glob`, then compare these libraries and alternatives to determine if dependencies could use a single, better solution. #### Large Dependencies Large dependencies often contain more functionality than needed for your specific use case. The solution typically involves: * Contributing upstream to split the dependency into more granular modules (but not excessively granular) * Migrating to a leaner, more focused dependency In such cases, look for lighter and potentially faster alternatives. ### Tip: Finding Dependents of a Package After identifying a package with replacement potential, create an issue in our [cleanup-repository](https://github.com/e18e/ecosystem-issues) to inform the community. Next, use [fuzzyma's tool](https://github.com/fuzzyma/e18e-tools) to find package dependents. The tool's README provides usage documentation. Here's an example that displays the top 100 dependents of `lodash`: ```sh npx github:Fuzzyma/e18e-tools lodash -n 100 -U https://npm.devminer.xyz/live_registry ``` You can post the results as Markdown in your GitHub issue and then create PRs for affected projects: ```sh npx github:Fuzzyma/e18e-tools lodash -n 100 -q -o md -U https://npm.devminer.xyz/live_registry > md-output.md ``` --- --- url: /learn/speedup.md description: Profiling and improving performance of popular projects across the ecosystem. --- # Speedup A major part of improving the ecosystem for everyone is *speed*. The speed of core packages, tools, and more can always be improved and will benefit the community as a whole. A few groups and individuals are working in this space. Some examples include: * [Speeding up the JavaScript Ecosystem](https://marvinh.dev/blog/speeding-up-javascript-ecosystem) ## Linting Some performance improvements can be detected via a linter. ### ESLint For those of us using ESLint, a few plugins are available which strongly align with the principals of the e18e effort: | Plugin | Description | | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | | [@e18e/eslint-plugin](https://github.com/e18e/eslint-plugin) | Official e18e plugin with rules for modernization, module replacements, and performance | | [eslint-plugin-barrel-files](https://github.com/thepassle/eslint-plugin-barrel-files) | Detects barrel/index files | ### Biome Biome supports some rules out of the box which align with e18e: * [noBarrelFile](https://biomejs.dev/linter/rules/no-barrel-file/) * [noReExportAll](https://biomejs.dev/linter/rules/no-re-export-all/) * [noRestrictedDependencies](https://biomejs.dev/linter/rules/no-restricted-dependencies/) (module-replacements) ### Oxlint Oxlint also supports a rule out of the box to prevent barrel files: * [oxc/no-barrel-file](https://oxc.rs/docs/guide/usage/linter/rules/oxc/no-barrel-file.html) ## Coding Tips The code you write plays an important role in the performance of your app. Some patterns are highlighted below for common pitfalls and ways to improve it. Remember to always profile your code when making performance changes! ### Avoid generators for hot code paths At the moment, most JavaScript engines do not optimize [generator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator) function calls, which leads to a large performance hit if used extensively. Prefer using non-async [iterators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator) or plain arrays. ### Avoid chaining array methods Chaining array methods like `map`, `filter`, `reduce`, etc. leads to many intermediate arrays being created and disposed, causing more work for the garbage collector. Each chain also leads to an extra iteration, which can be more times than needed. Prefer using [`for` loops](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for), [`for...in` loops](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in), [`for...of` loops](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of) and [iterator methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator#instance_methods), which are lazy, or a single chain method only to prevent the caveats above. --- --- url: /learn/levelup.md description: Modern, lighter alternatives to established tools and libraries. --- # Levelup Modern runtimes provide features that enable packages to be leaner, faster, and more focused. Just like esbuild offers a lean alternative when you don't need webpack's full customizability, these packages are designed to be **fast and small**. Projects contributing significantly to this effort include: * [Tinylibs](https://github.com/tinylibs) * [unjs](https://github.com/unjs/) * [es-tooling](https://github.com/es-tooling/) The levelup and cleanup efforts frequently intersect, as these focused packages reduce install size while delivering performance improvements. --- --- url: /docs.md --- # Community Documentation As part of the performance improvements effort, the community has produced various useful tools and guides. Please use the sidebar on the left to navigate through the available resources. If there are any additional tools or guides you think should be included, let us know in the Discord or GitHub discussions! --- --- url: /docs/publishing.md --- # Best Practices on Publishing npm Packages ## Introduction As a maintainer, there are many options available to you on how you setup your publishing workflow. This may be manual, semi-automated, or fully automated. With the rise of supply-chain attacks, it is more important than ever to ensure that your publishing workflow is secure. Maintainers need to take extra care to ensure malicious code and people cannot compromise their packages via the publishing workflow. In this document, we will cover a typical workflow with security in mind, and some recipes for more advanced workflows. ## Prerequisites ### Enforcing 2FA Before anything, it is important to enable 2FA in your various accounts and use a secure authentication method. In your npm account, navigate to the `Account` page and ensure you have 2FA enabled. Ideally, use a security key, otherwise use an authenticator app. Similarly, in your GitHub account, navigate to `Settings > Password and authentication` and ensure you have 2FA enabled. Again, ideally use a security key, otherwise use an authenticator app. We also highly recommend you manage your passwords with a password manager, and ideally generate them. > \[!NOTE] > A security key isn't necessarily a physical key (e.g. YubiKey), it can also be something your OS provides, like Windows Hello or macOS Touch ID. ### Setting up the GitHub repository Next, we need to ensure your GitHub repository is setup securely. #### Actions Settings Navigate to `Settings > Actions > General` and configure: * ✓ Require actions to be pinned to a full-length commit SHA * ✓ Require approval for first-time contributors * ✓ Set default workflow permissions to "Read repository contents and packages permissions" These settings ensure that outside contributors cannot run arbitrary code in your workflows without your approval first, and that all actions used in your workflows are pinned to a specific commit SHA, preventing supply-chain attacks through action updates. > \[!NOTE] > If your repository is part of an organization, we highly recommend you make these changes to the organisation as a whole rather than this specific repository. > This will ensure all repositories in the organisation have a consistent security measures in place. #### Branch Protection Navigate to `Settings > Branches` and configure: * ✓ Create a ruleset for your default branch (e.g. `main`) * ✓ Require a pull request before merging * ✓ Require approvals (set to at least 1) * ✓ Dismiss stale pull request approvals when new commits are pushed * ✓ Require approval of the most recent reviewable push This ensures that nobody can push directly to the `main` branch, and that all changes are reviewed before being merged. #### Secrets & Variables Navigate to `Settings > Secrets & Variables > Actions`: * Remove all unnecessary repository secrets (e.g. npm tokens, as OIDC can be used instead) ### Setting up Trusted Publishing Now we need to configure trusted publishing for our npm package. This will ensure that only workflows we trust can publish new versions of our package. Navigate to your npm package page, and click on the "Settings" tab. You will now see a "Trusted Publishing" section. In this "Trusted Publishing" section, setup a trusted publisher for the workflow you're about to create with the following settings: * Organisation and repository are the ones you're publishing from * Workflow filename is the name of the workflow file you'll create (e.g. `publish.yml`) * Check the `Allow npm stage publish` box only While you're in there, also check the box for "Require two-factor authentication and disallow tokens (recommended)". This will ensure that manual publishing *must* use 2FA. > \[!TIP] > You can also configure these settings from the npm CLI. To trust a GitHub Actions workflow and allow staged publishing, run: > > ```bash > npm trust github [package-name] --repo OWNER/REPO --file WORKFLOW_FILENAME.yml --allow-stage-publish > ``` > > To require 2FA for publishing and disallow token-based publishing, run: > > ```bash > npm access set mfa=publish [package-name] > ``` > \[!NOTE] > Make sure your GitHub workflow is using Node.js v24.8.0 or higher for the publish step. This ensures automatic trusted publishing is supported for npm. Alternatively, you can force npm to update by running `npm i -g npm` in a step in the workflow before you publish. ### Staged Packages You may have noticed the `Allow npm stage publish` option in the trusted publishing settings. This means that when a workflow publishes a new version of your package, it will be published to the npm staging area rather than being immediately released. Once this happens, you will need to visit the staging page: ``` https://www.npmjs.com/settings//staged-packages ``` In this page, you can review and approve staged packages before they are released. ## Standard Workflow Next, we need to create the GitHub workflow that will handle the publishing. For this, let's use a template from the [setup-publish](https://github.com/e18e/setup-publish) repository: [`publish.yml`](https://github.com/e18e/setup-publish/blob/main/templates/default.yml) In this file, you'll see we have the following jobs: * Test (runs tests) * Build (builds the package) * Publish (publishes the already built package) There are a few important things to note about this workflow: * The package is built in a separate job to publishing, ensuring the publish permissions are not exposed to runtime build code * All actions are pinned to a full-length commit SHA * Install is done with `--ignore-scripts` to avoid running any lifecycle scripts that may be malicious * Versions are *staged* to npm rather than directly published, which means you need to visit the npm staging page and approve them manually (with 2FA) When updating the workflow, keep these constraints in mind to ensure the security of your publishing process. > \[!TIP] > It is also worth setting `ignore-scripts=true` in your project's `.npmrc` file so this applies to all installs, not just in the workflow. > Similarly, we highly recommend you do this for your local user using `npm config set -g ignore-scripts true`. ### Creating a Release To publish a new version of your package, you can now create a tag against main: ```bash git tag v1.0.0 git push origin v1.0.0 ``` Then in the GitHub UI, navigate to the "Releases" page and click "Draft a new release". You can now choose the tag you just pushed, and click "Generate release notes" to automatically generate the changelog from your commit history. Now, click "Publish release" to trigger the workflow and stage your new package version. Finally, visit the npm staging page to review and approve the staged package, which will then be released. ## Alternative Workflows The [`@e18e/setup-publish`](https://github.com/e18e/setup-publish) CLI can help you set up the above workflow and a few other useful workflows in your repository. You can use it with `npx`: ```bash npx @e18e/setup-publish ``` This CLI uses a set of community defined [workflow templates](https://github.com/e18e/setup-publish/blob/main/templates) to help guide you in setting up your own workflow. ### Using changesets for versioning and changelog generation If you'd prefer to use [changesets](https://github.com/changesets/changesets) to manage your versioning and changelog generation, you can use the [changesets template](https://github.com/e18e/setup-publish/blob/main/templates/changesets.yml). This template differs from the basic workflow in a few ways: * Releases are created by merging a generated changesets release pull request to `main` * All other merged pull requests will automatically update the release pull request * The changelog is generated by changesets, and included in the release pull request * You no longer need to manually create releases or tags in GitHub ### Using changelogithub for changelog generation You can also use [changelogithub](https://github.com/antfu/changelogithub) to generate your changelog from commit messages. You can set this up using the [changelogithub template](https://github.com/e18e/setup-publish/blob/main/templates/changelogithub.yml). In this workflow: * You must still push tags manually * Any time a tag is pushed, a GitHub release is created with a changelog generated from commit messages * Any time a tag is pushed, the package is staged (to the npm staging area) ## Maintenance & Tooling ### Managing dependency updates We also highly recommend setting up [dependabot](https://docs.github.com/en/code-security/dependabot) or [renovate](https://www.whitesourcesoftware.com/free-developer-tools/renovate/) to keep your dependencies up to date. This will ensure that any security vulnerabilities in your dependencies are addressed promptly. ### Keeping actions up to date It is important to keep your GitHub actions up to date, and always reference them using a full-length commit SHA. To assist with switching to using full-length commit SHAs, you can use the [`actions-up`](https://github.com/azat-io/actions-up) CLI. ```bash npx actions-up ``` This will update your workflow files to use the latest commit SHA for each action. Once you've made the switch, you can then use dependabot, renovate or `actions-up` itself to keep your actions up to date. ### Linting workflows for issues and vulnerabilities You can also use [`zizmor`](https://github.com/zizmorcore/zizmor) to lint your GitHub workflows for common issues. This will find things like template injection vulnerabilities, and excessive permission scopes. For example, to lint your `publish.yml` workflow: ```bash zizmor .github/workflows/publish.yml ``` ### Validating package configuration Use the [`publint`](https://github.com/publint/publint) tool to lint your package for common publishing issues. This will find things like missing files, incorrect `package.json` fields, and much more. You can view the full list of rules in the [publint documentation](https://publint.dev/rules). To run this, simply execute: ```bash npx publint ``` ### Visualising dependency changes You can use [`multiocular`](https://github.com/multiocular-com/multiocular) to visualise changes in your dependencies between versions. This will give you a visualisation of what code has changed when a dependency updated, which can help you identify potential security issues and breaking changes. ## Further Security ### Use a GitHub environment You can use Github environments to enforce branch protection rules and prevent bypassing code reviews. > \[!IMPORTANT] > This is crucial if you use oidc trusted publishing in a repository where many accounts have write access. > > Without it, every account with write access to your repository can publish unreviewed changes to the registry by creating a new branch and modifying the release workflow Environments are managed under `https://github.com//settings/environments` * Create one for publishing e.g. `publish` * Do not allow administrators bypass protection rules * Limit it to only allow your active release branches (e.g. `main`, `v1`, `v2`). Do not use patterns, hardcode exact branch names and remove them from the environment when they are no longer active. * Add the `environment` key in your publish job ```yaml [publish.yml] jobs: publish: environment: publish ``` * Update your npm package trusted publisher settings on `npmjs.com/package//access` to include the environment name > \[!TIP] > You can also configure the `publish` environment in GitHub to require manual approval before the job can proceed. > > This ensures that even if someone manages to add bad code to a release branch and trigger the workflow, a human still has to review and approve the job before it can publish. ### Use hardware security keys Physical security keys (like YubiKey) provide strong 2FA protection and are generally much more secure than using an authenticator app or SMS. ### Use protection rules in GitHub We already noted that `main` should have a protection rule to prevent unreviewed changes. It would also be a good idea to add similar rules to any other long-lived branches you may have, and to *all* tags. ### Use immutable releases Enable [immutable releases](https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/immutable-releases) on GitHub and this will prevent any changes to tags or GitHub releases after they are created. This will ensure that once a release is created, it cannot be modified or deleted. --- --- url: /docs/replacements.md --- # Replacements When using the [e18e ESLint plugin](https://github.com/e18e/eslint-plugin) or the [CLI](https://github.com/e18e/cli), you may be suggested replacement modules for dependencies you have. This is a list of replacements, along with their migration guides and any useful information. > \[!NOTE] > The e18e ESLint plugin replaces [eslint-plugin-depend](https://github.com/es-tooling/eslint-plugin-depend) and includes its module-replacement functionality (the `ban-dependencies` rule), as well as many other performance related rules. ## What are these? The e18e community maintains a list of modules and their recommended replacements via the [module-replacements](https://github.com/e18e/module-replacements) repository. Typically, a module might be flagged as replaceable if it: * Is no longer actively maintained * Has more performant or modern alternatives * Has known security vulnerabilities * Lacks support for modern JavaScript features (e.g. ES Modules, TypeScript) Where it was not possible to contribute upstream to the existing modules, replacement modules have been created by the wider community in most cases. ## List of replaceable modules > \[!NOTE] > This is not an exhaustive list - similar modules to the ones listed here can often be replaced by roughly the same steps. | Module | Auto-fixable | | --------------------------------------------------------------------- | ----------------------- | | [`@jsdevtools/ez-spawn`](./ez-spawn.md) | :x: | | [`axios`](./fetch.md) | :x: | | [`bcrypt`](./bcrypt.md) | :x: | | [`better-auth-cli`](./better-auth-cli.md) | :x: | | [`bluebird`](./bluebird-q.md) | :x: | | [`body-parser`](./body-parser.md) | :x: | | [`buf-compare`](./buf-compare.md) | :x: | | [`buffer-equal`](./buffer-equal.md) | :x: | | [`buffer-equal-constant-time`](./buffer-equal-constant-time.md) | :x: | | [`buffer-equals`](./buffer-equals.md) | :x: | | [`builtin-modules`](./builtin-modules.md) | :x: | | [`chalk`](./chalk.md) | :ballot\_box\_with\_check: | | [`clean-webpack-plugin`](./clean-webpack-plugin.md) | :x: | | [`cli-builders`](./cli-builders.md) | :x: | | [`clipboardy`](./clipboardy.md) | :x: | | [`collection-map`](./collection-map.md) | :x: | | [`concat-stream`](./concat-stream.md) | :x: | | [`core-util-is`](./core-util-is.md) | :x: | | [`cosmiconfig`](./cosmiconfig.md) | :x: | | [`cpx`](./cpx.md) | :x: | | [`create-hmac`](./create-hmac.md) | :x: | | [`cross-spawn`](./cross-spawn.md) | :x: | | [`crypto-js`](./crypto-js.md) | :x: | | [`css-loader`](./css-loader.md) | :x: | | [`cwd`](./cwd.md) | :x: | | [`debug`](./debug.md) | :x: | | [`deep-equal`](./deep-equal.md) | :ballot\_box\_with\_check: | | [`deep-merge`](./deep-merge.md) | :x: | | [`depcheck`](./depcheck.md) | :x: | | [`desm`](./desm.md) | :x: | | [`detect-package-manager`](./detect-package-manager.md) | :x: | | [`dev-null`](./dev-null.md) | :x: | | [`dot-prop`](./dot-prop.md) | :x: | | [`dotenv`](./dotenv.md) | :x: | | [`duplexer`](./duplexer.md) | :x: | | [`emoji-regex`](./emoji-regex.md) | :x: | | [`error-stack-parser`](./error-stack-parser.md) | :x: | | [`eslint-config-airbnb`](./eslint-config-airbnb.md) | :x: | | [`eslint-plugin-es`](./eslint-plugin-es.md) | :x: | | [`eslint-plugin-eslint-comments`](./eslint-plugin-eslint-comments.md) | :x: | | [`eslint-plugin-import`](./eslint-plugin-import.md) | :x: | | [`eslint-plugin-node`](./eslint-plugin-node.md) | :x: | | [`eslint-plugin-react`](./eslint-plugin-react.md) | :x: | | [`eslint-plugin-vitest`](./eslint-plugin-vitest.md) | :x: | | [`event-stream`](./event-stream.md) | :x: | | [`execa`](./execa.md) | :x: | | [`express`](./express.md) | :x: | | [`extend`](./extend.md) | :x: | | [`ez-spawn`](./ez-spawn.md) | :x: | | [`faker`](./faker.md) | :x: | | [`fast-glob`](./fast-glob.md) | :x: | | [`feather`](./feather.md) | :x: | | [`feathers-hooks-common`](./feathers-hooks-common.md) | :x: | | [`find-cache-dir`](./find-cache-dir.md) | :x: | | [`find-cache-directory`](./find-cache-directory.md) | :x: | | [`find-file-up`](./find-file-up.md) | :x: | | [`find-pkg`](./find-pkg.md) | :x: | | [`find-process`](./find-process.md) | :x: | | [`find-up`](./find-up.md) | :x: | | [`fs-extra`](./fs-extra.md) | :x: | | [`get-stream`](./get-stream.md) | :x: | | [`glob`](./glob.md) | :x: | | [`globby`](./globby.md) | :x: | | [`graphemer`](./graphemer.md) | :x: | | [`graphviz`](./graphviz.md) | :x: | | [`gray-matter`](./gray-matter.md) | :x: | | [`gzip-size`](./gzip-size.md) | :x: | | [`has-yarn`](./has-yarn.md) | :x: | | [`html-minifier`](./html-minifier.md) | :x: | | [`http-proxy`](./http-proxy.md) | :x: | | [`img-colour`](./img-colour.md) | :x: | | [`inherits`](./inherits.md) | :x: | | [`invariant`](./invariant.md) | :x: | | [`is-builtin-module`](./is-builtin-module.md) | :ballot\_box\_with\_check: | | [`is-path-in-cwd`](./is-path-in-cwd.md) | :x: | | [`is-path-inside`](./is-path-inside.md) | :x: | | [`is-utf8`](./is-utf8.md) | :x: | | [`istanbul-lib-coverage`](./istanbul-lib-coverage.md) | :x: | | [`istanbul-lib-instrument`](./istanbul-lib-instrument.md) | :x: | | [`jQuery`](./jquery.md) | :x: | | [`js-yaml`](./js-yaml.md) | :x: | | [`jsonwebtoken`](./jsonwebtoken.md) | :x: | | [`jsx-ast-utils`](./jsx-ast-utils.md) | :x: | | [`jszip`](./jszip.md) | :x: | | [`lodash-underscore`](./lodash-underscore.md) | :x: | | [`materialize-css`](./materialize-css.md) | :x: | | [`md5`](./md5.md) | :ballot\_box\_with\_check: | | [`micromatch`](./micromatch.md) | :x: | | [`mime`](./mime.md) | :x: | | [`mississippi`](./mississippi.md) | :x: | | [`mkdirp`](./mkdirp.md) | :x: | | [`mockdate`](./mockdate.md) | :x: | | [`moment`](./moment.md) | :x: | | [`node-telegram-bot-api`](./node-telegram-bot-api.md) | :x: | | [`npm-check`](./npm-check.md) | :x: | | [`npm-run-all`](./npm-run-all.md) | :x: | | [`object-hash`](./object-hash.md) | :x: | | [`open`](./open.md) | :x: | | [`ora`](./ora.md) | :x: | | [`pako`](./pako.md) | :x: | | [`parseargs`](./parseargs.md) | :x: | | [`path-exists`](./path-exists.md) | :x: | | [`path-root`](./path-root.md) | :x: | | [`pbkdf2`](./pbkdf2.md) | :x: | | [`pkg-dir`](./pkg-dir.md) | :x: | | [`portal-vue`](./portal-vue.md) | :x: | | [`portfinder`](./portfinder.md) | :x: | | [`qs`](./qs.md) | :x: | | [`react-helmet`](./react-helmet.md) | :x: | | [`read-package-up`](./read-package-up.md) | :x: | | [`read-pkg`](./read-pkg.md) | :x: | | [`read-pkg-up`](./read-pkg-up.md) | :x: | | [`readable-stream`](./readable-stream.md) | :x: | | [`resolve`](./resolve.md) | :x: | | [`rimraf`](./rimraf.md) | :x: | | [`rollup-plugin-babel`](./rollup-plugin-babel.md) | :x: | | [`rollup-plugin-commonjs`](./rollup-plugin-commonjs.md) | :x: | | [`rollup-plugin-node-resolve`](./rollup-plugin-node-resolve.md) | :x: | | [`rollup-plugin-terser`](./rollup-plugin-terser.md) | :x: | | [`schema-validation`](./schema-validation.md) | :x: | | [`semver`](./semver.md) | :x: | | [`shortid`](./shortid.md) | :x: | | [`slice-ansi`](./slice-ansi.md) | :x: | | [`sort-object`](./sort-object.md) | :x: | | [`source-map-explorer`](./source-map-explorer.md) | :x: | | [`split`](./split.md) | :x: | | [`sqlite3`](./sqlite3.md) | :x: | | [`stream-buffers`](./stream-buffers.md) | :x: | | [`string-width`](./string-width.md) | :x: | | [`strip-ansi`](./strip-ansi.md) | :x: | | [`tar-fs`](./tar-fs.md) | :x: | | [`tempy`](./tempy.md) | :x: | | [`through`](./through.md) | :x: | | [`timezone-support`](./timezone-support.md) | :x: | | [`tokml`](./tokml.md) | :x: | | [`traverse`](./traverse.md) | :ballot\_box\_with\_check: | | [`tsc`](./tsc.md) | :x: | | [`tsup`](./tsup.md) | :x: | | [`tweetnacl`](./tweetnacl.md) | :x: | | [`typescript-native-preview`](./typescript-native-preview.md) | :x: | | [`uri-js`](./uri-js.md) | :x: | | [`utf8`](./utf8.md) | :x: | | [`uuidv4`](./uuidv4.md) | :x: | | [`vue3-markdown-it`](./vue3-markdown-it.md) | :x: | | [`wait-for-expect`](./wait-for-expect.md) | :x: | | [`wellknown`](./wellknown.md) | :x: | | [`wrap-ansi`](./wrap-ansi.md) | :x: | | [`xmldom`](./xmldom.md) | :x: | | [`yaml-ast-parser`](./yaml-ast-parser.md) | :x: | --- --- url: /docs/cli.md --- # CLI The e18e CLI is a powerful tool for analyzing and optimizing your JavaScript/TypeScript projects. It helps you identify performance issues, find optimization opportunities, and automatically migrate to suggested dependencies. ## Quick Start ```sh # Install and run analysis npx @e18e/cli analyze # Migrate packages interactively npx @e18e/cli migrate --interactive ``` ## Installation You can install the e18e CLI globally or use it with npx: ::: code-group ```sh [npm] npm install -g @e18e/cli ``` ```sh [yarn] yarn global add @e18e/cli ``` ```sh [pnpm] pnpm add -g @e18e/cli ``` ```sh [bun] bun add -g @e18e/cli ``` ::: ## Commands ### `analyze` Analyzes your project for various issues and provides recommendations for optimization. ```sh e18e-cli analyze [path] [options] ``` **Arguments:** * `path` (optional) - Path to a directory or tarball file to analyze. Defaults to current directory. **Options:** * `--pack ` - Package manager to use for packing. Options: `auto`, `npm`, `yarn`, `pnpm`, `bun`, `none`. Default: `auto` * `--log-level ` - Set the log level. Options: `debug`, `info`, `warn`, `error`. Default: `info` * `--manifest ` - Path(s) to custom manifest file(s) for module replacements analysis **Examples:** ```sh # Analyze current project e18e-cli analyze # Analyze with debug output e18e-cli analyze --log-level debug # Analyze with specific package manager e18e-cli analyze --pack pnpm ``` ### `migrate` Migrates your project from packages to more performant alternatives. ```sh e18e-cli migrate [packages...] [options] ``` **Arguments:** * `packages...` - Names of packages to migrate (e.g., `chalk`, `lodash`) **Options:** * `--dry-run` - Don't apply any fixes, only show what would change * `--include ` - Files to migrate. Default: `**/*.{ts,js}` * `--interactive` - Run in interactive mode **Examples:** ```sh # Migrate specific packages e18e-cli migrate chalk lodash # Preview changes without applying e18e-cli migrate chalk --dry-run # Interactive mode to select packages e18e-cli migrate --interactive ``` ## Replacements System The CLI uses a set of codemods from the [module-replacements-codemods](https://github.com/es-tooling/module-replacements-codemods) project for migrations, and replacements from the [replacements list](https://e18e.dev/docs/replacements.html). ### Default Replacements The CLI comes with pre-configured replacements for common performance optimizations. You can find the list of the current replacements in the [replacements docs](https://e18e.dev/docs/replacements.html). > \[!IMPORTANT] > The replacement list is still being developed and expanded. The current list represents the initial set of well-tested migrations. More replacements will be added as they are validated and tested across the ecosystem. ### Custom Manifests You can bring your own replacement rules by providing custom manifest files: ```json { "replacements": [ { "moduleName": "custom-module", "type": "simple", "replacement": "Use picocolors, kleur, or native console styling" } ] } ``` Use custom manifests with the `--manifest` option: ```sh e18e-cli analyze --manifest ./my-replacements.json ``` ## Troubleshooting ### Common Issues **"No package.json found"** * Make sure you're running the command from a project directory * Check that package.json exists in the current or specified directory **"Path must be a tarball file or a directory"** * Ensure the path you're providing is valid * For tarballs, make sure the file has a `.tgz` extension **Permission errors** * Try running with `npx` instead of global installation * Check file permissions in your project directory ### Debug Mode For detailed debugging information, use the `--log-level debug` option: ```sh e18e-cli analyze --log-level debug ``` This will show detailed information about what the CLI is doing and help identify issues. ## Learn more Want the full reference, options, and how the tool works? See the full documentation in the **[repository](https://github.com/e18e/cli)**. --- --- url: /docs/replacements/lodash-underscore.md description: >- Modern alternatives for Lodash for array/object manipulation and common programming tasks --- # `lodash` / `underscore` ## You don’t (may not) need Lodash/Underscore Here you could read how to replace Lodash or Underscore in your project. [Website](https://you-dont-need.github.io/You-Dont-Need-Lodash-Underscore) ## es-toolkit [es-toolkit](https://es-toolkit.dev/) is a utility library similar to lodash that is designed to replace lodash by offering a seamless compat layer. It supports tree shaking out of the box and offers better performances for modern JavaScript runtimes. --- --- url: /docs/replacements/fetch.md description: >- Shared alternatives and examples for fetch based HTTP clients used across related modules --- # Fetch-based HTTP clients (shared) This page contains the common, recommended alternatives and examples for fetch based HTTP clients used by `axios`, `node-fetch`, and `cross-fetch` replacement docs. ## `fetch` API (native) The native [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) API is available in Node.js (since v18.x) and all modern browsers. For many use cases it replaces `axios`/`node-fetch`/`cross-fetch` without adding dependencies. > \[!TIP] > Node.js provides a codemod which can migrate some of this automatically, see the docs at [@nodejs/axios-to-whatwg-fetch](https://app.codemod.com/registry/@nodejs/axios-to-whatwg-fetch) for more details. Example: ```ts // GET const res = await fetch('https://api.example.com/data') const data = await res.json() // POST await fetch('https://api.example.com/data', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key: 'value' }) }) ``` ## `ofetch` [`ofetch`](https://github.com/unjs/ofetch) is a small fetch wrapper with automatic JSON parsing, request/response interceptors, and retry support. Example: ```ts import { ofetch } from 'ofetch' const api = ofetch.create({ baseURL: 'https://api.example.com' }) const data = await api('/user', { query: { id: 123 } }) const created = await api('/items', { method: 'POST', body: { name: 'A' } }) ``` ## `ky` [`ky`](https://github.com/sindresorhus/ky) is a lightweight HTTP client built on top of the Fetch API. It adds convenience features like timeouts, hooks (interceptors), and a simpler API surface. Example: ```ts import ky from 'ky' const api = ky.create({ prefixUrl: 'https://api.example.com', timeout: 5000 // ms }) const data = await api.get('users').json() ``` --- --- url: /docs/replacements/better-auth-cli.md description: Modern alternative to the @better-auth/cli package for the Better Auth CLI --- # Replacements for `@better-auth/cli` ## `auth` The Better Auth CLI is now published under the `auth` name, and `@better-auth/cli` is deprecated, so switch the dependency over. ```json { "devDependencies": { "@better-auth/cli": "1.4.21", // [!code --] "auth": "1.7.3" // [!code ++] } } ``` ```bash npx @better-auth/cli generate # [!code --] npx auth generate # [!code ++] ``` --- --- url: /docs/replacements/img-colour.md description: >- Modern alternatives to the @img/colour package for color parsing and manipulation --- # Replacements for `@img/colour` ## `color` [`@img/colour`](https://github.com/lovell/colour) is not a fork or alternative. It is an esbuild bundle of [`color`](https://github.com/Qix-/color) (and its dependencies `color-convert` and `color-string`) regenerated for CommonJS. Depending on `color` directly gives you the same API and dedupes with the many packages already using `color`, `color-convert` and `color-string`. Example: ```ts import Color from '@img/colour' // [!code --] import Color from 'color' // [!code ++] const red = Color('red').hex() ``` --- --- url: /docs/replacements/ez-spawn.md description: Modern alternatives to the ez-spawn package for spawning child processes --- # Replacements for `@jsdevtools/ez-spawn` ## `tinyexec` `ez-spawn` accepts shell-like command strings, which `tinyexec` does not. For example: ```ts import ezSpawn from '@jsdevtools/ez-spawn' // [!code --] import { x } from 'tinyexec' // [!code ++] await ezSpawn.async('ls -l') // [!code --] await x('ls', ['-l']) // [!code ++] ``` Alternatively, you can use [`args-tokenizer`](https://github.com/TrySound/args-tokenizer/) to convert a shell string to a command and arguments: ```ts import ezSpawn from '@jsdevtools/ez-spawn' // [!code --] import { tokenizeArgs } from 'args-tokenizer' // [!code ++] import { x } from 'tinyexec' // [!code ++] const [command, ...args] = tokenizeArgs('ls -l') // [!code ++] await ezSpawn.async('ls -l') // [!code --] await x(command, args) // [!code ++] ``` --- --- url: /docs/replacements/typescript-native-preview.md description: >- Modern alternative to the @typescript/native-preview package for the native version of TypeScript --- # Replacements for `@typescript/native-preview` ## `typescript` TypeScript v7 is no longer a preview and is now published under the `typescript` name, so `@typescript/native-preview` is no longer needed ```json { "dependencies": { "@typescript/native-preview": "7.0.0-dev.20260707.2", // [!code --] "typescript": "7.0.2" // [!code ++] } } ``` --- --- url: /docs/replacements/bcrypt.md description: Modern alternatives to the bcrypt package for password hashing --- # Replacements for `bcrypt` The `bcrypt` package can be replaced by native functionality in most runtimes, or more performant packages. ## `bcryptjs` [`bcryptjs`](https://github.com/dcodeIO/bcrypt.js) is a widely used pure JavaScript implementation with a very similar API surface to `bcrypt`. ```ts import bcrypt from 'bcrypt' // [!code --] import bcrypt from 'bcryptjs' // [!code ++] const salt = await bcrypt.genSalt(10) const hash = await bcrypt.hash('password', salt) ``` ## `node:crypto` (native, Node.js built-in) Node provides [`node:crypto`](https://nodejs.org/api/crypto.html) for secure password hashing primitives (for example `scrypt` and `pbkdf2`). This is not a drop-in `bcrypt` API replacement, but is a good option if you can switch to a more secure cryptographic algorithm it supports. ## Web Crypto API (native) The [Web Crypto API](https://developer.mozilla.org/docs/Web/API/Web_Crypto_API) provides native functionality for cryptographic operations in both web browsers and Node. > \[!NOTE] > A few legacy algorithms are intentionally omitted for security reasons (e.g. MD5). ## Bun (built-in) Bun supports the Web Crypto API natively, and also provides support for streaming hashing via [`Bun.CryptoHasher`](https://bun.sh/docs/api/hashing). As with the Web Crypto API, many legacy algorithms are intentionally omitted for security reasons (e.g. MD5). --- --- url: /docs/replacements/bluebird-q.md description: >- Modern alternatives to the Bluebird and Q Promise libraries for async control flow in JavaScript --- # Replacements for `bluebird` / `q` ## `Promise` (native) [`bluebird`](https://github.com/petkaantonov/bluebird?tab=readme-ov-file#%EF%B8%8Fnote%EF%B8%8F) and [`q`](https://github.com/kriskowal/q#note) recommend switching away from them to native promises. ## `nativebird` [`NativeBird`](https://github.com/doodlewind/nativebird) is an ultralight native `Promise` extension that provides Bluebird-like helpers if you miss a few conveniences from Bluebird. --- --- url: /docs/replacements/body-parser.md description: >- Modern alternatives to the body-parser package for parsing HTTP request bodies in Node.js servers --- # Replacements for `body-parser` ## `milliparsec` [`milliparsec`](https://github.com/tinyhttp/milliparsec) is a lightweight alternative to [`body-parser`](https://github.com/expressjs/body-parser) with a smaller footprint. Example: ```ts import bodyParser from 'body-parser' // [!code --] import { json, urlencoded } from 'milliparsec' // [!code ++] import express from 'express' const app = express() app.use(bodyParser.json()) // [!code --] app.use(bodyParser.urlencoded({ extended: true })) // [!code --] app.use(json()) // [!code ++] app.use(urlencoded()) // [!code ++] ``` For API differences and feature comparison, see the [migration.md](https://github.com/tinyhttp/milliparsec/blob/master/migration.md). --- --- url: /docs/replacements/buf-compare.md description: Native Node.js alternatives to the buf-compare package for buffer comparison --- # Replacements for `buf-compare` ## `Buffer.compare` (native) [`Buffer.compare`](https://nodejs.org/api/buffer.html#static-method-buffercomparebuf1-buf2) is a native method which achieves the same result as `buf-compare`, available since Node v0.11.13. Example: ```ts import { Buffer } from 'node:buffer' import bufCompare from 'buf-compare' // [!code --] const buf1 = Buffer.from('303') const buf2 = Buffer.from('808') bufCompare(buf1, buf2) // [!code --] Buffer.compare(buf1, buf2) // [!code ++] ``` --- --- url: /docs/replacements/buffer-equal-constant-time.md description: >- Native Node.js alternatives to the buffer-equal-constant-time package for safe buffer equality checks --- # Replacements for `buffer-equal-constant-time` ## `crypto.timingSafeEqual` (native) You can use the [`timingSafeEqual`](https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b) function from the `node:crypto` module. Example: ```ts import { Buffer } from 'node:buffer' import bufferEqual from 'buffer-equal-constant-time' // [!code --] import * as crypto from 'node:crypto' // [!code ++] const bufUser = Buffer.from('303') const bufSecret = Buffer.from('303') bufferEqual(bufUser, bufSecret) // [!code --] bufUser.length === bufSecret.length // [!code ++] ? crypto.timingSafeEqual(bufUser, bufSecret) // [!code ++] : !crypto.timingSafeEqual(bufUser, bufUser) // [!code ++] ``` --- --- url: /docs/replacements/buffer-equal.md description: >- Native Node.js alternatives to the buffer-equal package for buffer equality checks --- # Replacements for `buffer-equal` ## `Buffer#equals` (native) Buffers have an [`equals`](https://nodejs.org/api/buffer.html#bufequalsotherbuffer) method since Node v0.12. Example: ```ts import { Buffer } from 'node:buffer' import bufferEqual from 'buffer-equal' // [!code --] const buf1 = Buffer.from('303') const buf2 = Buffer.from('303') bufferEqual(buf1, buf2) // [!code --] buf1.equals(buf2) // [!code ++] ``` --- --- url: /docs/replacements/buffer-equals.md description: >- Native Node.js alternatives to the buffer-equals package for buffer equality checks --- # Replacements for `buffer-equals` ## `Buffer#equals` (native) Buffers have an [`equals`](https://nodejs.org/api/buffer.html#bufequalsotherbuffer) method since Node v0.12. Example: ```ts import { Buffer } from 'node:buffer' import bufferEquals from 'buffer-equals' // [!code --] const buf1 = Buffer.from('303') const buf2 = Buffer.from('303') bufferEquals(buf1, buf2) // [!code --] buf1.equals(buf2) // [!code ++] ``` --- --- url: /docs/replacements/builtin-modules.md description: >- Native Node.js alternatives to the builtin-modules package for listing built-in modules --- # Replacements for `builtin-modules` ## `builtinModules` (native, since Node.js 6.x) For getting the list of built-in modules, you can use [`builtinModules`](https://nodejs.org/api/module.html#modulebuiltinmodules): ```ts import builtinModulesList from 'builtin-modules' // [!code --] import { builtinModules } from 'node:module' // [!code ++] builtinModulesList.includes('fs') // true [!code --] builtinModules.includes('fs') // true [!code ++] ``` --- --- url: /docs/replacements/chalk.md description: >- Modern alternatives to the chalk package for terminal string styling and colors, with notes on browser console support --- # Replacements for `chalk` ## `styleText` (native) Since Node 20.x, you can use the [`styleText`](https://nodejs.org/api/util.html#utilstyletextformat-text-options) function from the `node:util` module to style text in the terminal. > \[!TIP] > Node.js provides a codemod which can migrate some of this automatically, see the docs at [@nodejs/chalk-to-util-styletext](https://app.codemod.com/registry/@nodejs/chalk-to-util-styletext) for more details. Example: ```ts import { styleText } from 'node:util' // [!code ++] import chalk from 'chalk' // [!code --] console.log(`Hello ${chalk.blue('blue')} world!`) // [!code --] console.log(`Hello ${styleText('blue', 'blue')} world!`) // [!code ++] console.log(`I am ${chalk.hex('#ec8f5e')('hex color')}!`) // [!code --] console.log(`I am ${styleText('#ec8f5e', 'hex color')}!`) // [!code ++] ``` When using multiple styles, you can pass an array to `styleText`: ```ts console.log(`I am ${chalk.blue.bgRed('blue on red')}!`) // [!code --] console.log(`I am ${styleText(['blue', 'bgRed'], 'blue on red')}!`) // [!code ++] ``` > \[!NOTE] > Before Node v26.1.0, `styleText` did not support hex colors or RGB colors (e.g. `#EFEFEF` or `255, 239, 235`). Hex colors are supported starting in v26.1.0, but RGB colors are still not supported. You can view the available styles in the [Node documentation](https://nodejs.org/api/util.html#modifiers). ## `picocolors` [`picocolors`](https://github.com/alexeyraspopov/picocolors) follows a similar API but without chaining: ```ts import chalk from 'chalk' // [!code --] import picocolors from 'picocolors' // [!code ++] console.log(`Hello ${chalk.blue('blue')} world!`) // [!code --] console.log(`Hello ${picocolors.blue('blue')} world!`) // [!code ++] // A chained example console.log(chalk.blue.bgRed('blue on red')) // [!code --] console.log(picocolors.blue(picocolors.bgRed('blue on red'))) // [!code ++] ``` > \[!NOTE] > `picocolors` currently does not support RGB and hex colors (e.g. `#EFEFEF` or `255, 239, 235`). ## `ansis` [`ansis`](https://github.com/webdiscus/ansis/) supports a chaining syntax similar to chalk and supports both RGB, and hex colors. Example: ```ts import ansis from 'ansis' // [!code ++] import chalk from 'chalk' // [!code --] console.log(`Hello ${chalk.blue('blue')} world!`) // [!code --] console.log(`Hello ${ansis.blue('blue')} world!`) // [!code ++] ``` When using multiple styles, you can chain them just like in chalk: ```ts console.log(chalk.blue.bgRed('blue on red')) // [!code --] console.log(ansis.blue.bgRed('blue on red')) // [!code ++] ``` Similarly, you can use RGB and hex colors: ```ts console.log(chalk.rgb(239, 239, 239)('Hello world!')) // [!code --] console.log(ansis.rgb(239, 239, 239)('Hello world!')) // [!code ++] ``` ## Browser support While these libraries are primarily designed for terminal output, some projects may need colored output in browser environments. Following [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/console#styling_console_output), the native approach is `%c` directive in `console.log`: ```ts console.log( 'Hello %ce%c1%c8%ce', 'color: #ec8f5e;', 'color: #f2ca60;', 'color: #bece57;', 'color: #7bb560;', 'Ecosystem Performance' ) ``` Library support: * [`ansis`](https://github.com/webdiscus/ansis#browser-compatibility-for-ansi-codes) - colors are supported in *Chromium* browsers * `picocolors` - strips colors in browser environments * `node:util` - is not available in browsers --- --- url: /docs/replacements/clean-webpack-plugin.md description: Modern alternatives to the clean-webpack-plugin package --- # Replacements for `clean-webpack-plugin` ## `output.clean` (built-in, since webpack 5) Example: ```js const { CleanWebpackPlugin } = require('clean-webpack-plugin') // [!code --] module.exports = { plugins: [new CleanWebpackPlugin()], // [!code --] output: { clean: true } // [!code ++] } ``` --- --- url: /docs/replacements/clipboardy.md description: >- Modern alternatives to the clipboardy package for copy/pasting on Node.js and in the browser --- # Replacements for `clipboardy` ## `tinyclip` [`tinyclip`](https://github.com/tinylibs/tinyclip) provides cross-platform clipboard functionality to Node.js. ```js import clipboard from 'clipboardy' // [!code --] import * as clipboard from 'tinyclip' // [!code ++] await clipboard.write('hello world') // [!code --] await clipboard.writeText('hello world') // [!code ++] await clipboard.read() // [!code --] await clipboard.readText() // [!code ++] ``` > \[!NOTE] > `writeSync()` and `readSync()` do not have any equivalent in `tinyclip`, we recommend you migrate to their async versions. ## Clipboard API (native, browser) Learn more about the [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API). ```js import clipboard from 'clipboardy' // [!code --] await clipboard.write('hello world') // [!code --] await navigator.clipboard.writeText('hello world') // [!code ++] await clipboard.read() // [!code --] await navigator.clipboard.readText() // [!code ++] ``` > \[!NOTE] > `writeSync()` and `readSync()` do not have an equivalent in browsers. --- --- url: /docs/replacements/collection-map.md description: Native alternatives to the collection-map package --- # Replacements for `collection-map` ## Objects ```js const map = require('collection-map') // [!code --] const data = { a: 'foo', b: 'bar', c: 'baz' } // Mapping values const res = map(data, (item) => item) // [!code --] const res = Object.values(data).map((item) => item) // [!code ++] // Mapping keys const res = map(data, (item, key) => key) // [!code --] const res = Object.keys(data).map((key) => key) // [!code ++] // Mapping keys and values const res = map(data, (item, key) => fn(item, key)) // [!code --] const res = Object.fromEntries( // [!code ++] Object.entries(data).map(([key, value]) => fn(value, key)) // [!code ++] ) // [!code ++] ``` ## Arrays ```js const map = require('collection-map') // [!code --] const data = ['foo', 'bar', 'baz'] // Mapping items const res = map(data, (item) => item) // [!code --] const res = data.map((item) => item) // [!code ++] // Mapping indices const res = map(data, (item, index) => index) // [!code --] const res = data.map((_, index) => index) // [!code ++] // Mapping items indices const res = map(data, (item, index) => fn(item, index)) // [!code --] const res = data.map((item, index) => fn(item, index)) // [!code ++] ``` ## Property Strings When passing a string to extract a specific property, use a standard map with property access. ```js const map = require('collection-map') // [!code --] const obj = { a: { aaa: 'one', bbb: 'four', ccc: 'seven' }, b: { aaa: 'two', bbb: 'five', ccc: 'eight' }, c: { aaa: 'three', bbb: 'six', ccc: 'nine' } } // Objects: Map values to property map(obj, 'aaa') // [!code --] Object.values(obj).map((item) => item.aaa) // [!code ++] // Arrays: Map items to property const array = [obj.a, obj.b, obj.c] map(array, 'bbb') // [!code --] array.map((item) => item.bbb) // [!code ++] ``` ## `thisArg` (Context) Native `.map()` supports a second argument for `thisArg`. ```js const map = require('collection-map') // [!code --] const array = ['a', 'b', 'c'] const ctx = { a: 'aaa', b: 'bbb', c: 'ccc' } const res = map(array, function(item) { // [!code --] const res = array.map(function(item) { // [!code ++] return this[item] }, ctx) ``` --- --- url: /docs/replacements/concat-stream.md description: Modern alternatives to the concat-stream package --- # Replacements for `concat-stream` ## Utility Consumers (native, since Node.js 16.7.0) ### Consuming as a string ```js import concat from 'concat-stream' // [!code --] import { text } from 'node:stream/consumers' // [!code ++] readable.pipe(concat((data) => { // [!code --] const str = data.toString('utf-8') // [!code --] console.log(str) // [!code --] })) // [!code --] const str = await text(readable) // [!code ++] console.log(str) // [!code ++] ``` ### Consuming as Buffer ```js import concat from 'concat-stream' // [!code --] import { buffer } from 'node:stream/consumers' // [!code ++] readable.pipe(concat((data) => { // [!code --] console.log(data.length) // [!code --] })) // [!code --] const data = await buffer(readable) // [!code ++] console.log(data.length) // [!code ++] ``` ### Consuming as an Array ```js import concat from 'concat-stream' // [!code --] import { toArray } from 'node:stream/consumers' // [!code ++] readable.pipe(concat({ encoding: 'object' }, (items) => { // [!code --] console.log(items) // [!code --] })) // [!code --] const items = await toArray(readable) // [!code ++] console.log(items) // [!code ++] ``` Alternatively, using an async iterable loop: ```js for await (const chunk of readable) { // ... } ``` --- --- url: /docs/replacements/core-util-is.md description: Native Node.js alternatives to the core-util-is package --- # Replacements for `core-util-is` ## `util.types` (native, Node.js) [`util.types`](https://nodejs.org/api/util.html#utiltypes) is an official, cross‑realm type checks for built-in objects (Date, RegExp, Error, typed arrays, etc.) Example: ```ts import * as cui from 'core-util-is' // [!code --] import { types } from 'node:util' // [!code ++] const isDate = cui.isDate(value) // [!code --] const isDate = types.isDate(value) // [!code ++] ``` --- --- url: /docs/replacements/cosmiconfig.md description: Modern alternatives to the cosmiconfig package --- # Replacements for `cosmiconfig` ## `lilconfig` [`lilconfig`](https://github.com/antonk52/lilconfig) is a zero-dependency alternative to cosmiconfig with the same API > \[!NOTE] > `lilconfig` does not support YAML files out of the box unless you provide custom loaders. ### Example ```js import { lilconfig, lilconfigSync } from 'lilconfig' const options = { searchPlaces: [ 'package.json', // Will use "myapp" field '.myapprc', '.myapprc.json', 'myapp.config.js' ] } await lilconfig('myapp', options).search() // Sync version lilconfigSync('myapp', options).search() ``` ### Loading YAML files ```js import { lilconfig } from 'lilconfig' import { parse } from 'yaml' const yamlLoader = (filepath, content) => parse(content) const options = { searchPlaces: [ 'package.json', '.myapprc', '.myapprc.yaml', '.myapprc.yml', 'myapp.config.js' ], loaders: { '.yaml': yamlLoader, '.yml': yamlLoader, noExt: yamlLoader } } await lilconfig('myapp', options).search() ``` --- --- url: /docs/replacements/cpx.md description: Modern alternatives to the cpx package for copying file globs with watch mode --- # Replacements for `cpx` ## `cpx2` [`cpx`](https://github.com/mysticatea/cpx) is unmaintained. [`cpx2`](https://github.com/bcomnes/cpx2) is an actively maintained fork that keeps the same CLI bin name (`cpx`), so it works as a drop-in replacement for CLI usage. For the Node API, switch your import to `cpx2`. ```sh npm i -D cpx # [!code --] npm i -D cpx2 # [!code ++] # CLI stays the same (bin name is still "cpx") cpx "src/**/*.{html,png,jpg}" app --watch ``` Node API replacement: ```ts const cpx = require('cpx') // [!code --] const cpx = require('cpx2') // [!code ++] cpx.copy('src/**/*.js', 'dist', (err) => { if (err) throw err }) ``` --- --- url: /docs/replacements/create-hmac.md description: Modern alternatives to the `create-hmac` package for HMAC --- # Replacements for `create-hmac` ## `crypto.createHmac` (native, Node.js) ```js import { createHmac } from 'node:crypto' // [!code ++] import createHmac from 'create-hmac' // [!code --] const secret = 'secret-key' const data = 'message-to-sign' const hash = createHmac('sha256', secret).update(data).digest('hex') ``` ## `crypto.subtle.sign` (native, browser) ```js const encoder = new TextEncoder() async function generateHMAC(secret, data) { const key = await crypto.subtle.importKey( 'raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'] ) const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(data)) return new Uint8Array(signature).toHex() } const secret = 'secret-key' const data = 'message-to-sign' const hash = await generateHMAC(secret, data) ``` ## `@noble/hashes` ```js import { hmac } from '@noble/hashes/hmac.js' // [!code ++] import { sha256 } from '@noble/hashes/sha256.js' // [!code ++] import { bytesToHex } from '@noble/hashes/utils.js' // [!code ++] import createHmac from 'create-hmac' // [!code --] const secret = 'secret-key' const data = 'message-to-sign' const hash = createHmac('sha256', secret).update(data).digest('hex') // [!code --] const hash = bytesToHex(hmac(sha256, secret, data)) // [!code ++] ``` --- --- url: /docs/replacements/cross-spawn.md description: Modern alternatives to the cross-spawn package for spawning child processes --- # Replacements for `cross-spawn` > \[!NOTE] > If your application does not need to support Windows, you can likely drop `cross-spawn` entirely as it solely exists to provide Windows spawn compatibility. You can use the built-in `node:child_process` in that case. ## `tinyexec` [`tinyexec`](https://github.com/tinylibs/tinyexec) is a minimal, dependency-free process execution library. `cross-spawn` follows the Node.js `spawn` API and returns a `ChildProcess`. In comparison, `tinyexec` returns an awaitable result containing the process output and exit code. Example: ```ts import spawn from 'cross-spawn' // [!code --] import { x } from 'tinyexec' // [!code ++] const child = spawn('npm', ['list', '-g']) // [!code --] const { stdout, exitCode } = await x('npm', ['list', '-g']) // [!code ++] child.stdout?.on('data', (output) => console.log(output.toString())) // [!code --] console.log(stdout, exitCode) // [!code ++] ``` --- --- url: /docs/replacements/crypto-js.md description: Modern alternatives to the `crypto-js` package for cryptographic operations --- # Replacements for `crypto-js` `crypto-js` is no longer actively maintained and has been discontinued since engines now come with this functionality built-in. ## `node:crypto` (native, Node.js built-in) Node provides a [`node:crypto`](https://nodejs.org/api/crypto.html) module as part of its standard library. This supports hashes/HMAC, AES-GCM, PBKDF2/scrypt, RSA/ECDSA/Ed25519, and secure RNG. ```ts import sha256 from 'crypto-js/sha256' // [!code --] import { createHash } from 'node:crypto' // [!code ++] const secret = 'abcdefg' const hash = sha256(secret).toString() // [!code --] const hash = createHash('sha256') // [!code ++] .update(secret) // [!code ++] .digest('hex') // [!code ++] ``` ## Web Crypto API (native) The [Web Crypto API](https://developer.mozilla.org/docs/Web/API/Web_Crypto_API) provides native functionality for cryptographic operations in both web browsers and Node. > \[!NOTE] > A few legacy algorithms are intentionally omitted for security reasons (e.g. MD5). ## Bun (built-in) Bun supports the Web Crypto API natively, and also provides support for streaming hashing via [`Bun.CryptoHasher`](https://bun.sh/docs/api/hashing). As with the Web Crypto API, many legacy algorithms are intentionally omitted for security reasons (e.g. MD5). --- --- url: /docs/replacements/css-loader.md description: Modern alternatives to the css-loader package --- # Replacements for `css-loader` `css-loader` is deprecated. webpack parses CSS itself, so the loader can be dropped from your config along with `style-loader` and `mini-css-extract-plugin`. ## Built-in CSS support Example: ```js const MiniCssExtractPlugin = require('mini-css-extract-plugin') // [!code --] module.exports = { module: { rules: [ { test: /\.css$/i, use: [MiniCssExtractPlugin.loader, 'css-loader'] } // [!code --] ] }, plugins: [new MiniCssExtractPlugin()] // [!code --] } ``` > \[!NOTE] > Preprocessors still need their loaders. Sass, Less and PostCSS are not covered by built-in CSS support. --- --- url: /docs/replacements/cwd.md description: Modern alternatives to the cwd package for resolving project-relative paths --- # Replacements for `cwd` ## `empathic` [`empathic`](https://github.com/lukeed/empathic) provides a more generic way to find files and directories upwards. [`cwd`](https://github.com/jonschlinkert/cwd) is not `process.cwd()`. It finds the nearest `package.json` and returns an absolute path anchored to that package root. Unlike [`pkg-dir`](./pkg-dir.md), which returns the package root directory, `cwd` returns a resolved path relative to that root. The main difference is that `empathic` is *synchronous*, so you should no longer `await` the result. Example: ```ts import { resolve, dirname } from 'node:path' // [!code ++] import * as pkg from 'empathic/package' // [!code ++] import cwd from 'cwd' // [!code --] const filepath = resolve('one/two.js') // [!code ++] const pkgPath = pkg.up({ cwd: filepath }) // [!code ++] const result = cwd('one/two.js') // [!code --] const result = pkgPath ? resolve(dirname(pkgPath), filepath) : filepath // [!code ++] ``` --- --- url: /docs/replacements/debug.md description: Modern alternatives to the debug package --- # Replacements for `debug` ## `obug` [`obug`](https://github.com/sxzz/obug) is a lightweight JavaScript debugging utility, forked from debug, featuring TypeScript and ESM support. `obug` v1 retains compatibility with debug, but drops support for older browsers and Node.js versions, making it a drop-in replacement. `obug` v2 refactors some API imports and usage for better support of ESM and TypeScript, easier customization, and an even smaller package size. ```ts import debug from 'debug' // [!code --] import { createDebug } from 'obug' // [!code ++] const logger = debug('name') // [!code --] const logger = createDebug('name') // [!code ++] ``` --- --- url: /docs/replacements/deep-equal.md description: Modern alternatives to the deep-equal package for deep object comparison --- # Replacements for `deep-equal` ## `util.isDeepStrictEqual` (native, since Node.js v9.0.0) Node.js has a builtin function [`isDeepStrictEqual`](https://nodejs.org/api/util.html#utilisdeepstrictequalval1-val2-options). Consider using that if you don’t need browser support. Example: ```ts import equal from 'deep-equal' // [!code --] import { isDeepStrictEqual } from 'node:util' // [!code ++] const a = { foo: 'bar' } const b = { foo: 'bar' } equal(a, b) // true [!code --] isDeepStrictEqual(a, b) // true [!code ++] ``` ## `dequal` [`dequal`](https://github.com/lukeed/dequal) has the same simple API as `deep-equal`. Example: ```ts import equal from 'deep-equal' // [!code --] import dequal from 'dequal' // [!code ++] const a = { foo: 'bar' } const b = { foo: 'bar' } equal(a, b) // true [!code --] dequal(a, b) // true [!code ++] ``` ## `Bun.deepEquals` (native, Bun) Bun has a built-in [`Bun.deepEquals`](https://bun.com/docs/runtime/utils#bun-deepequals) function. It accepts two values to compare, and an optional `strict` flag (default `false`). Example: ```ts import equal from 'deep-equal' // [!code --] const a = { foo: 'bar' } const b = { foo: 'bar' } equal(a, b) // true [!code --] Bun.deepEquals(a, b) // true [!code ++] // Strict Mode equal(a, b, { strict: true }) // true [!code --] Bun.deepEquals(a, b, true) // true [!code ++] ``` --- --- url: /docs/replacements/depcheck.md description: >- Modern alternatives to depcheck for analyzing project dependencies and unused code --- # Replacements for `depcheck` ## `knip` [knip](https://github.com/webpro-nl/knip) is a more actively maintained and feature-rich alternative to [`depcheck`](https://github.com/depcheck/depcheck). In most cases, knip works out of the box without any configuration - just run `npx knip`. For projects that need customization, you can create a configuration file. Example: ```json { "$schema": "https://unpkg.com/knip@5/schema.json", "ignoreDependencies": ["@types/*", "eslint-*"] } ``` --- --- url: /docs/replacements/desm.md description: Native alternatives to the desm package --- # Replacements for `desm` ## `import.meta.dirname` and `import.meta.filename` ```ts import { join } from 'desm' // [!code --] import { join } from 'node:path' // [!code ++] join('./foo.ts') // [!code --] join(import.meta.dirname, './foo.ts') // [!code ++] ``` ```ts import { dirname } from 'desm' // [!code --] dirname(import.meta.url) // [!code --] import.meta.dirname // [!code ++] ``` ```ts import { filename } from 'desm' // [!code --] filename(import.meta.url) // [!code --] import.meta.filename // [!code ++] ``` --- --- url: /docs/replacements/detect-package-manager.md description: >- Lightweight alternative to detect-package-manager for detecting the package manager with zero dependencies --- # Replacements for `detect-package-manager` ## `package-manager-detector` [`package-manager-detector`](https://github.com/antfu-collective/package-manager-detector) is a lightweight alternative for detecting the package manager being used in a project. It has zero dependencies, is ESM-only, and provides utilities such as constructing commands for different package managers. Example migration from [`detect-package-manager`](https://www.npmjs.com/package/detect-package-manager): ```ts import { detect } from 'detect-package-manager' // [!code --] import { detect } from 'package-manager-detector' // [!code ++] const pm = await detect() // [!code --] const result = await detect() // [!code ++] const pm = result?.name // [!code ++] ``` > \[!NOTE] > `package-manager-detector` returns an object with `name` and `agent` properties, while `detect-package-manager` returns a string directly. Example migration from [`preferred-pm`](https://www.npmjs.com/package/preferred-pm): ```ts import { preferredPM } from 'preferred-pm' // [!code --] import { detect } from 'package-manager-detector' // [!code ++] const result = await preferredPM() // [!code --] const result = await detect() // [!code ++] ``` Example migration from [`which-pm`](https://www.npmjs.com/package/which-pm): ```ts import { whichPM } from 'which-pm' // [!code --] import { detect } from 'package-manager-detector' // [!code ++] const result = await whichPM() // [!code --] const result = await detect({ // [!code ++] // Include the `install-metadata` strategy to have the package manager // [!code ++] // that's used for installation take precedence // [!code ++] strategies: [ // [!code ++] 'install-metadata', // [!code ++] 'lockfile', // [!code ++] 'packageManager-field', // [!code ++] 'devEngines-field' // [!code ++] ] // [!code ++] }) // [!code ++] ``` --- --- url: /docs/replacements/dev-null.md description: Modern alternatives to the dev-null package --- # Replacements for `dev-null` ## `stream.Writable` (native, Node.js) The native [`stream.Writable`](https://nodejs.org/api/stream.html#class-streamwritable) class can be used to construct a custom writable stream that discards incoming data. ```ts import devnull from 'dev-null' // [!code --] import { Writable } from 'node:stream' // [!code ++] stream.pipe(devnull()) // [!code --] const empty = new Writable({ // [!code ++] write(chunk, encoding, callback) { // [!code ++] callback() // [!code ++] } // [!code ++] }) // [!code ++] stream.pipe(empty) // [!code ++] ``` --- --- url: /docs/replacements/dot-prop.md description: >- Modern alternatives to the dot-prop package for getting, setting, and deleting nested object properties using dot notation --- # Replacements for `dot-prop` ## `dlv` and `dset` [`dlv`](https://github.com/developit/dlv) gets nested values with default fallbacks and [`dset`](https://github.com/lukeed/dset) sets nested values with automatic intermediate object creation. ```ts import { getProperty, setProperty } from 'dot-prop' // [!code --] import delve from 'dlv' // [!code ++] import { dset } from 'dset' // [!code ++] const value = getProperty(obj, 'foo.bar.baz') // [!code --] const value = delve(obj, 'foo.bar.baz') // [!code ++] setProperty(obj, 'foo.bar.baz', 'value') // [!code --] dset(obj, 'foo.bar.baz', 'value') // [!code ++] ``` ## `String.prototype.split` + `Array.prototype.reduce` (native) If you only need to get a nested value you can use a simple function: ```js function getProperty(obj, key) { return key.split('.').reduce((acc, k) => acc?.[k], obj) } const value = getProperty(obj, 'foo.bar.baz') ``` > \[!NOTE] > This assumes that you do not consume dot paths as user input. > If you do, ensure you sanitize keys before accessing them (e.g. through `Object.hasOwn`). --- --- url: /docs/replacements/dotenv.md description: >- Native Node.js alternatives to the dotenv package for loading and managing .env files in Node.js --- # Replacements for `dotenv` ## `process.loadEnvFile` (native, since Node.js v20.12.0) [`process.loadEnvFile`](https://nodejs.org/api/process.html#processloadenvfilepath) (Node.js v20.12.0+) loads a `.env` file into `process.env` at runtime. It throws if the file is missing. Defaults to `.env` in the current working directory when called without a path. ```ts import dotenv from 'dotenv' // [!code --] import process from 'node:process' // [!code ++] dotenv.config({ path: '.env' }) // [!code --] process.loadEnvFile('.env') // [!code ++] ``` ## `--env-file` / `--env-file-if-exists` (native, Node.js) [`--env-file`](https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#--env-fileconfig) (Node.js v20.6.0+) and [`--env-file-if-exists`](https://nodejs.org/docs/latest-v22.x/api/cli.html#--env-file-if-existsfile) (Node.js v22.9.0+) can be passed as command-line flags to load environment variables from a specified file. `--env-file` throws if the file is missing. If the file may be absent, use `--env-file-if-exists`. ```bash node --env-file=.env index.js ``` Also supported by: * [tsx](https://github.com/privatenumber/tsx) * [Bun](https://bun.sh/docs/runtime/env#manually-specifying-env-files) * [Deno](https://docs.deno.com/runtime/reference/env_variables/#.env-file) Remove dotenv preload: ```ts import 'dotenv/config' // [!code --] // No import needed when using --env-file ``` Remove explicit dotenv config: ```ts import dotenv from 'dotenv' // [!code --] dotenv.config({ path: '.env' }) // [!code --] // No runtime configuration needed ``` In package.json scripts: ```json { "scripts": { "start": "node index.js", // [!code --] "start": "node --env-file=.env index.js" // [!code ++] } } ``` ## Node.js `parseEnv` [`parseEnv`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilparseenvcontent) (Node.js v20.12.0+) can be used to parse a `.env` string into an object without loading it into `process.env`. ```ts import { parse } from 'dotenv' // [!code --] import { parseEnv } from 'node:util' // [!code ++] const envContent = '...' const env = parse(envContent) // [!code --] const env = parseEnv(envContent) // [!code ++] ``` If the `.env` is a Node.js Buffer, convert it to a string first with `.toString()`. --- --- url: /docs/replacements/duplexer.md description: Modern alternatives to the duplexer package --- # Replacements for `duplexer` ## `Duplex.from` (native, Node.js) ```js import duplexer from 'duplexer' // [!code --] import { Duplex } from 'node:stream' // [!code ++] duplexer(writableStream, readableStream) // [!code --] Duplex.from({ // [!code ++] writable: writableStream, // [!code ++] readable: readableStream // [!code ++] }) // [!code ++] ``` --- --- url: /docs/replacements/emoji-regex.md description: >- Modern alternatives to the emoji-regex package for emoji detection and matching --- # Replacements for `emoji-regex` ## `emoji-regex-xs` [`emoji-regex-xs`](https://github.com/slevithan/emoji-regex-xs) offers the same API and features whilst being 98% smaller. ```ts import emojiRegex from 'emoji-regex' // [!code --] import emojiRegex from 'emoji-regex-xs' // [!code ++] const text = ` \u{231A}: ⌚ default emoji presentation character (Emoji_Presentation) \u{2194}\u{FE0F}: ↔️ default text presentation character rendered as emoji \u{1F469}: 👩 emoji modifier base (Emoji_Modifier_Base) \u{1F469}\u{1F3FF}: 👩🏿 emoji modifier base followed by a modifier ` const regex = emojiRegex() for (const match of text.matchAll(regex)) { const emoji = match[0] console.log(`Matched sequence ${emoji} — code points: ${[...emoji].length}`) } ``` ## Unicode RegExp (native) If your target runtime supports ES2024 Unicode property sets, you can use the native [`\p{RGI_Emoji}`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Unicode_character_class_escape) property in a regular expression. This relies on the engine's built‑in Unicode handling. ```ts import emojiRegex from 'emoji-regex' // [!code --] const regex = emojiRegex() // [!code --] const regex = /\p{RGI_Emoji}/gv // [!code ++] for (const match of text.matchAll(regex)) { const emoji = match[0] console.log(`Matched sequence ${emoji} — code points: ${[...emoji].length}`) } ``` --- --- url: /docs/replacements/error-stack-parser.md description: Modern alternatives to error-stack-parser for parsing error stack traces --- # Replacements for `error-stack-parser` ## `error-stack-parser-es` [`error-stack-parser-es`](https://github.com/antfu/error-stack-parser-es) is a smaller, maintained, ESM port of `error-stack-parser` with the same stack frame output. `error-stack-parser` exposes a default export with a `parse` method. With `error-stack-parser-es`, use the named `parse` export instead. Example: ```ts import ErrorStackParser from 'error-stack-parser' // [!code --] import { parse } from 'error-stack-parser-es' // [!code ++] const frames = ErrorStackParser.parse(error) // [!code --] const frames = parse(error) // [!code ++] ``` If you only need the raw parsed lines and not the full stack frame objects, the `error-stack-parser-es/lite` entry provides an even lighter parser. --- --- url: /docs/replacements/eslint-config-airbnb.md description: Modern, maintained alternative to the eslint-config-airbnb shared config --- # Replacements for `eslint-config-airbnb` ## `eslint-config-airbnb-extended` [`eslint-config-airbnb-extended`](https://github.com/eslint-config/airbnb-extended) is a more modern, actively maintained successor to [`eslint-config-airbnb`](https://github.com/airbnb/javascript). It supports both JavaScript and TypeScript, and includes rules for React, JSX, and React Hooks, and supports ESLint v9/10 configs. It consolidates `eslint-config-airbnb-base`, `eslint-config-airbnb`, and `eslint-config-airbnb-typescript` into a single config, only supports flat config (`eslint.config.mjs`), and uses a faster, modern equivalent of the `import` plugin (`eslint-plugin-import-x`). The upstream project provides an official [migration guide](https://eslint-airbnb-extended.nishargshah.dev/migration/upgrade-to-extended) and a CLI scaffolder to set up a new config. Note that any rules referencing the old `import/*` plugin will need to be updated to use the `import-x/*` equivalents (e.g. `import/no-unresolved` -> `import-x/no-unresolved`). --- --- url: /docs/replacements/eslint-plugin-es.md description: >- Modern alternatives to the eslint-plugin-es package for ECMAScript feature linting --- # Replacements for `eslint-plugin-es` ## `eslint-plugin-es-x` [eslint-plugin-es-x](https://github.com/eslint-community/eslint-plugin-es-x) is a direct fork which is actively maintained. It has new features, bugfixes and updated dependencies. ```ts import { FlatCompat } from '@eslint/eslintrc' // [!code --] import pluginES from 'eslint-plugin-es' // [!code --] import pluginESx from 'eslint-plugin-es-x' // [!code ++] const compat = new FlatCompat() // [!code --] export default [ { files: ['**/*.js'], languageOptions: { ecmaVersion: 2020 }, plugins: { es: pluginES, // [!code --] 'es-x': pluginESx // [!code ++] }, rules: { 'es/no-regexp-lookbehind-assertions': 'error', // [!code --] 'es-x/no-regexp-lookbehind-assertions': 'error' // [!code ++] } }, ...compat.extends('plugin:es/restrict-to-es2018'), // [!code --] pluginESx.configs['flat/restrict-to-es2018'] // [!code ++] ] ``` If you're using a legacy config format: ```ts module.exports = { extends: [ 'eslint:recommended', 'plugin:es/restrict-to-es2018', // [!code --] 'plugin:es-x/restrict-to-es2018' // [!code ++] ], plugins: [ 'es', // [!code --] 'es-x' // [!code ++] ], rules: { 'es/no-regexp-lookbehind-assertions': 'error', // [!code --] 'es-x/no-regexp-lookbehind-assertions': 'error' // [!code ++] } } ``` --- --- url: /docs/replacements/eslint-plugin-eslint-comments.md description: >- Modern alternatives to the eslint-plugin-eslint-comments package for ESLint comment linting --- # Replacements for `eslint-plugin-eslint-comments` ## `@eslint-community/eslint-plugin-eslint-comments` [`@eslint-community/eslint-plugin-eslint-comments`](https://github.com/eslint-community/eslint-plugin-eslint-comments) is the actively maintained successor with updated dependencies, flat config support, and continued development. ```ts import eslintComments from 'eslint-plugin-eslint-comments' // [!code --] import commentsCommunity from '@eslint-community/eslint-plugin-eslint-comments/configs' // [!code ++] export default [ commentsCommunity.recommended, // [!code ++] { plugins: { 'eslint-comments': eslintComments // [!code --] }, rules: { 'eslint-comments/no-unused-disable': 'error', // [!code --] '@eslint-community/eslint-comments/no-unused-disable': 'error' // [!code ++] } } ] ``` If you're using a legacy config format: ```ts module.exports = { extends: [ 'eslint:recommended', 'plugin:eslint-comments/recommended', // [!code --] 'plugin:@eslint-community/eslint-comments/recommended' // [!code ++] ], rules: { 'eslint-comments/no-unused-disable': 'error', // [!code --] '@eslint-community/eslint-comments/no-unused-disable': 'error' // [!code ++] } } ``` --- --- url: /docs/replacements/eslint-plugin-import.md description: >- Modern alternative to eslint-plugin-import, which helps with linting of ES6+ import/export syntax --- # Replacements for `eslint-plugin-import` ## `eslint-plugin-import-x` [`eslint-plugin-import-x`](https://github.com/un-ts/eslint-plugin-import-x) is a modern fork of [`eslint-plugin-import`](https://github.com/import-js/eslint-plugin-import). `import-x` focuses on having a smaller dependency footprint and faster module resolution via a Rust-based resolver. ### Flat config ```ts import importPlugin from 'eslint-plugin-import' // [!code --] import { createNodeResolver, importX } from 'eslint-plugin-import-x' // [!code ++] import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript' // [!code ++] export default [ importPlugin.flatConfigs.recommended, // [!code --] importX.flatConfigs.recommended, // [!code ++] { settings: { 'import/resolver': { typescript: true }, // [!code --] 'import-x/resolver-next': [ // [!code ++] createTypeScriptImportResolver(), // [!code ++] createNodeResolver() // [!code ++] ] // [!code ++] }, rules: { 'import/no-unresolved': 'error', // [!code --] 'import-x/no-unresolved': 'error', // [!code ++] 'import/no-nodejs-modules': 'warn', // [!code --] 'import-x/no-nodejs-modules': 'warn' // [!code ++] } } ] ``` ### Legacy config ```ts module.exports = { extends: [ 'eslint:recommended', 'plugin:import/recommended', // [!code --] 'plugin:import-x/recommended', // [!code ++] 'plugin:import/typescript', // [!code --] 'plugin:import-x/typescript' // [!code ++] ], plugins: [ 'import', // [!code --] 'import-x' // [!code ++] ], settings: { 'import/resolver': { typescript: true }, // [!code --] 'import-x/resolver': { typescript: true } // [!code ++] }, rules: { 'import/no-unresolved': 'error', // [!code --] 'import-x/no-unresolved': 'error' // [!code ++] } } ``` ## Oxlint [Oxlint](https://oxc.rs/docs/guide/usage/linter.html) is a high-performance linter for JavaScript and TypeScript, built on the Rust-based `Oxc` compiler stack. It's intended to be fully backwards-compatible with ESLint, having ported most of the ESLint rules, as well as those from popular plugins including `eslint-plugin-import`. The migration process from ESLint is covered in [the Oxlint documentation](https://oxc.rs/docs/guide/usage/linter/migrate-from-eslint.html), and can be done automatically from an ESLint flat config using [`npx @oxlint/migrate`](https://github.com/oxc-project/oxlint-migrate). > \[!NOTE] > Oxlint is not necessarily a full drop-in replacement, as not all of the `eslint-plugin-import` rules have been, or will be, implemented. Check [the GitHub issue](https://github.com/oxc-project/oxc/issues/1117) to view the progress. --- --- url: /docs/replacements/eslint-plugin-node.md description: >- Modern alternatives to the eslint-plugin-node package for Node.js-specific linting rules --- # Replacements for `eslint-plugin-node` ## `eslint-plugin-n` [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n) is a direct fork which is actively maintained. It has new features, bugfixes and updated dependencies. ```ts import nPlugin from 'eslint-plugin-n' // [!code ++] import nodePlugin from 'eslint-plugin-node' // [!code --] export default [ { files: ['**/*.js'], // or any other pattern plugins: { node: nodePlugin, // [!code --] n: nPlugin // [!code ++] }, rules: { ...nodePlugin.configs['recommended-script'].rules, // [!code --] ...nPlugin.configs['recommended-script'].rules, // [!code ++] 'node/exports-style': ['error', 'module.exports'], // [!code --] 'n/exports-style': ['error', 'module.exports'] // [!code ++] } } ] ``` If you're using a legacy config format: ```ts module.exports = { extends: [ 'eslint:recommended', 'plugin:node/recommended', // [!code --] 'plugin:n/recommended' // [!code ++] ] } ``` --- --- url: /docs/replacements/eslint-plugin-react.md description: >- Modern alternatives to the eslint-plugin-react package for React/JSX-specific linting rules --- # Replacements for `eslint-plugin-react` ## `@eslint-react/eslint-plugin` [`@eslint-react/eslint-plugin`](https://github.com/Rel1cx/eslint-react) is not a drop-in replacement, but a feature‑rich alternative that covers many of the same (and additional) rules. Flat config example: ```ts import eslintReact from '@eslint-react/eslint-plugin' // [!code ++] import reactPlugin from 'eslint-plugin-react' // [!code --] export default [ { files: ['**/*.{jsx,tsx}'], plugins: { react: reactPlugin, // [!code --] '@eslint-react': eslintReact // [!code ++] }, rules: { ...reactPlugin.configs.recommended.rules, // [!code --] ...eslintReact.configs.recommended.rules, // [!code ++] 'react/no-unknown-property': 'error', // [!code --] '@eslint-react/dom/no-unknown-property': 'error' // [!code ++] } } ] ``` > \[!NOTE] > `@eslint-react/eslint-plugin` is not a drop‑in replacement. Use [their migration guide](https://www.eslint-react.xyz/docs/migrating-from-eslint-plugin-react) to map rules/options and automate changes where possible. ## Oxlint [Oxlint](https://oxc.rs/docs/guide/usage/linter.html) is a high-performance linter for JavaScript and TypeScript, built on the Rust-based `Oxc` compiler stack. It's intended to be fully backwards-compatible with ESLint, having ported most of the ESLint rules, as well as those from popular plugins including `eslint-plugin-react`. The migration process from ESLint is covered in [the Oxlint documentation](https://oxc.rs/docs/guide/usage/linter/migrate-from-eslint.html), and can be done automatically from an ESLint flat config using [`npx @oxlint/migrate`](https://github.com/oxc-project/oxlint-migrate). > \[!NOTE] > Oxlint is not necessarily a full drop-in replacement, as not all of the `eslint-plugin-react` rules have been, or will be, implemented. Check [the GitHub issue](https://github.com/oxc-project/oxc/issues/1022) to view the progress. --- --- url: /docs/replacements/eslint-plugin-vitest.md description: >- Modern alternatives to the eslint-plugin-vitest package for Vitest-specific linting rules --- # Replacements for `eslint-plugin-vitest` ## `@vitest/eslint-plugin` [`@vitest/eslint-plugin`](https://github.com/vitest-dev/eslint-plugin-vitest) is the same project as `eslint-plugin-vitest` but re-published under a different name. `eslint-plugin-vitest` is no longer maintained because the [original maintainer has lost access to their old npm account](https://github.com/vitest-dev/eslint-plugin-vitest/issues/537). ```ts import vitest from '@vitest/eslint-plugin' // [!code ++] import vitest from 'eslint-plugin-vitest' // [!code --] export default [ { files: ['tests/**'], // or any other pattern plugins: { vitest }, rules: { ...vitest.configs.recommended.rules, // you can also use vitest.configs.all.rules to enable all rules 'vitest/max-nested-describe': ['error', { max: 3 }] // you can also modify rules' behavior using option like this } } ] ``` --- --- url: /docs/replacements/event-stream.md description: Modern alternatives to the event-stream package --- # Replacements for `event-stream` ## `es.pipeline()` replacement ```ts import es from 'event-stream' // [!code --] import { pipeline } from 'node:stream/promises' // [!code ++] es.pipeline(s1, s2, s3) // [!code --] await pipeline(s1, s2, s3) // [!code ++] ``` ## `es.split()` replacement ```ts import es from 'event-stream' // [!code --] import { createReadStream } from 'node:fs' // [!code ++] import { createInterface } from 'node:readline' // [!code ++] createReadStream('file.txt').pipe(es.split()) // [!code --] const lines = createInterface({ input: createReadStream('file.txt') }) // [!code ++] ``` ## `es.map()` replacement ```ts import es from 'event-stream' // [!code --] readableStream.pipe( // [!code --] es.map((data, cb) => cb(null, fn(data))) // [!code --] ) // [!code --] readableStream.map((data) => fn(data)) // [!code ++] ``` ## `es.merge()` replacement ```ts import es from 'event-stream' // [!code --] import { PassThrough } from 'node:stream' // [!code ++] es.merge([s1, s2]) // [!code --] const combined = new PassThrough() // [!code ++] s1.pipe(combined, { end: false }) // [!code ++] s2.pipe(combined, { end: false }) // [!code ++] ``` --- --- url: /docs/replacements/execa.md description: Modern alternatives to the execa package for running child processes --- # Replacements for `execa` ## `tinyexec` [`tinyexec`](https://github.com/tinylibs/tinyexec) is a minimal process execution library. Example: ```ts import { execa } from 'execa' // [!code --] import { x } from 'tinyexec' // [!code ++] const { stdout } = await execa('ls', ['-l']) // [!code --] const { stdout } = await x('ls', ['-l'], { throwOnError: true }) // [!code ++] ``` ## `nanoexec` If you prefer a very thin wrapper over `child_process.spawn` (including full spawn options and optional shell), [`nanoexec`](https://github.com/fabiospampinato/nanoexec) is another light alternative. Its `stdout`/`stderr` are Buffers. Example: ```ts import { execa } from 'execa' // [!code --] import exec from 'nanoexec' // [!code ++] const { stdout } = await execa('echo', ['example']) // [!code --] const res = await exec('echo', ['example']) // [!code ++] const stdout = res.stdout.toString('utf8') // [!code ++] ``` ## Bun If you’re on Bun, its built-in [`$`](https://bun.com/reference/bun/$) template tag can replace `execa`’s script-style usage: Example: ```ts import { $ } from 'execa' // [!code --] import { $ } from 'bun' // [!code ++] const { stdout } = await $`echo "Hello"` // [!code --] const stdout = await $`echo "Hello"`.text() // [!code ++] ``` --- --- url: /docs/replacements/express.md description: Modern alternatives to the express package --- # Replacements for `express` Express has been the industry standard for years, but the ecosystem has shifted toward ESM-first, type-safe, and runtime-agnostic frameworks that offer significantly better performance and developer experience. ## `h3` [`h3`](https://github.com/h3js/h3) is a minimal H(TTP) framework built for high performance and portability. Example: ```ts import express from 'express' // [!code --] import { H3, defineHandler, toNodeHandler } from 'h3' // [!code ++] import { createServer } from 'node:http' // [!code ++] const app = express() // [!code --] const app = new H3() // [!code ++] app.get('/', (req, res) => res.send('Hello world')) // [!code --] app.get('/', defineHandler(() => 'Hello world')) // [!code ++] app.listen(3000) // [!code --] createServer(toNodeHandler(app)).listen(3000) // [!code ++] ``` ## `tinyhttp` [`tinyhttp`](https://github.com/tinyhttp/tinyhttp) is designed to be a drop-in replacement remaining compatible with many Express middlewares. Example: ```ts import express from 'express' // [!code --] import { App } from '@tinyhttp/app' // [!code ++] const app = express() // [!code --] const app = new App() // [!code ++] app.get('/', (req, res) => res.send('Hello world')) app.listen(3000) ``` ## `hono` [`hono`](https://github.com/honojs/hono) is a small, simple, and fast web framework for the Edges. Example: ```ts import express from 'express' // [!code --] import { Hono } from 'hono' // [!code ++] const app = express() // [!code --] const app = new Hono() // [!code ++] app.get('/', (req, res) => res.send('Hello world')) // [!code --] app.get('/', (context) => context.text('Hello world')) // [!code ++] export default app // [!code ++] ``` ## `elysia` If you are using Bun, [`elysia`](https://github.com/elysiajs/elysia) is often the best choice as it is specifically optimized for the Bun runtime. Example: ```ts import express from 'express' // [!code --] import { Elysia } from 'elysia' // [!code ++] const app = express() // [!code --] const app = new Elysia() // [!code ++] app.get('/', (req, res) => res.send('Hello world')) // [!code --] app.get('/', () => 'Hello world') // [!code ++] app.listen(3000) ``` --- --- url: /docs/replacements/extend.md description: Native alternatives to the extend package for deep cloning and merging objects --- # Replacements for `extend` ## `structuredClone` (native) If you only need to deep clone an object, you can use [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone): ```ts import extend from 'extend' // [!code --] extend(true, {}, config) // true [!code --] structuredClone(config) // true [!code ++] ``` ## Spread syntax If you need to merge two shallow objects, you can use [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#spread_in_object_literals): ```ts const obj1 = { foo: 'bar', x: 42 } const obj2 = { bar: 'baz', y: 13 } const mergedObj = { ...obj1, ...obj2 } ``` ## `defu` If you need to deep merge two objects, you can use [`defu`](https://github.com/unjs/defu): ```ts import { defu } from 'defu' const options = defu(object, ...defaults) ``` --- --- url: /docs/replacements/faker.md description: >- Modern replacements for the unmaintained faker package generating massive amounts of fake (but realistic) data --- # Replacements for `faker` ## `@faker-js/faker` [`@faker-js/faker`](https://github.com/faker-js/faker) is a direct, community‑maintained fork of `faker` with new features, bugfixes, modern ESM/CJS builds, and updated data/locales. ```ts const faker = require('faker') // [!code --] const { faker } = require('@faker-js/faker') // [!code ++] faker.datatype.boolean() faker.image.avatar() ``` --- --- url: /docs/replacements/fast-glob.md description: >- Modern alternatives to the fast-glob package for fast file system pattern matching --- # Replacements for `fast-glob` ## `tinyglobby` [`tinyglobby`](https://github.com/SuperchupuDev/tinyglobby) is a modern, lightweight alternative that provides similar functionality with better performance. Example: ```ts import fg from 'fast-glob' // [!code --] import { glob } from 'tinyglobby' // [!code ++] import process from 'node:process' const files = await fg('**/*.ts', { // [!code --] const files = await glob('**/*.ts', { // [!code ++] cwd: process.cwd(), ignore: ['**/node_modules/**'], expandDirectories: false // [!code ++] }) ``` Most options from `fast-glob` have direct equivalents in `tinyglobby`. Check the [tinyglobby documentation](https://superchupu.dev/tinyglobby/migration) for the complete list of supported options. ## `fs.glob` (native, since Node 22.x) [`fs.glob`](https://nodejs.org/api/fs.html#fspromisesglobpattern-options) is built into modern versions of Node. Example: ```ts import fg from 'fast-glob' // [!code --] import { glob } from 'node:fs/promises' // [!code ++] const files = await fg('src/**/*.ts', { // [!code --] const files = await Array.fromAsync(glob('src/**/*.ts', { // [!code ++] cwd, }) // [!code --] })) // [!code ++] ``` You can also iterate over the results asynchronously: ```ts for await (const result of glob('src/**/*.ts', { cwd })) { // result is an individual path console.log(result) } ``` > \[!NOTE] > Node's built-in `glob` is more minimal and does not support negation patterns or fine-grained options like setting a max depth out of the box. --- --- url: /docs/replacements/feather.md description: Modern alternatives to feather packages --- # Replacements for `feather-icons` and `react-feather` ## `lucide` for `feather-icons` [`lucide`](https://lucide.dev/) is a community-maintained fork of Feather Icons. Example: ```js import feather from 'feather-icons' // [!code --] import { icons } from 'lucide' // [!code ++] feather.icons.x.toSvg({ class: 'icon icon-x' }) // [!code --] icons.x.toSvg({ class: 'icon icon-x' }) // [!code ++] ``` ## `lucide-react` for `react-feather` [`lucide-react`](https://lucide.dev/guide/packages/lucide-react) provides React components for the Lucide icon set and is the natural replacement for `react-feather`. Example: ```jsx import { Camera, ArrowRight } from 'react-feather' // [!code --] import { Camera, ArrowRight } from 'lucide-react' // [!code ++] export function Header() { return ( <> ) } ``` > \[!NOTE] > Lucide provides information on the [comparison](https://lucide.dev/guide/comparison#lucide-vs-feather-icons) of it vs feather-icons --- --- url: /docs/replacements/feathers-hooks-common.md description: >- Modern alternatives to the feathers-hooks-common package for common hooks and utilities in Feathers services --- # Replacements for `feathers-hooks-common` ## `feathers-utils` [`feathers-utils`](https://github.com/feathersjs/feathers-utils) is the official successor of [`feathers-hooks-common`](https://github.com/feathersjs-ecosystem/feathers-hooks-common). It provides a modern set of hooks, utilities, predicates and transformers for Feathers v5, is fully typed and tree-shakeable. `feathers-utils` is ESM only and requires Node.js 22 or later. The migration is not a 1:1 mapping: some hooks were renamed, some were replaced by more explicit alternatives (e.g. `discard` and `keep` by `transformData`/`transformResult` with transformers), and some were removed. See the [migration guide](https://utils.feathersjs.com/migrating-from-feathers-hooks-common) for details on every hook and utility. Example: ```ts import { discard, iff, isProvider } from 'feathers-hooks-common' // [!code --] import { iff, transformResult } from 'feathers-utils/hooks' // [!code ++] import { isProvider } from 'feathers-utils/predicates' // [!code ++] import { omit } from 'feathers-utils/transformers' // [!code ++] app.service('users').hooks({ after: { all: [ iff(isProvider('external'), discard('password')) // [!code --] iff(isProvider('external'), transformResult((item) => omit(item, ['password']))) // [!code ++] ] } }) ``` --- --- url: /docs/replacements/find-cache-dir.md description: >- Modern alternatives to the find-cache-dir package for locating cache directories --- # Replacements for `find-cache-dir` ## `empathic` [`empathic`](https://github.com/lukeed/empathic) provides a more generic way to find files and directories upwards. Example: ```ts import * as pkg from 'empathic' // [!code ++] import findCacheDirectory from 'find-cache-dir' // [!code --] findCacheDirectory({ name: 'foo' }) // [!code --] pkg.cache('foo') // [!code ++] ``` --- --- url: /docs/replacements/find-cache-directory.md description: >- Modern alternatives to the find-cache-directory package for locating cache directories --- # Replacements for `find-cache-directory` ## `empathic` [`empathic`](https://github.com/lukeed/empathic) provides a more generic way to find files and directories upwards. Example: ```ts import * as pkg from 'empathic' // [!code ++] import findCacheDirectory from 'find-cache-directory' // [!code --] findCacheDirectory({ name: 'foo' }) // [!code --] pkg.cache('foo') // [!code ++] ``` --- --- url: /docs/replacements/find-file-up.md description: >- Modern alternatives to the find-file-up package for finding files by walking up parent directories --- # Replacements for `find-file-up` ## `empathic` [`empathic`](https://github.com/lukeed/empathic) provides a more generic way to find files and directories upwards. The main difference is that `empathic` is *synchronous*, so you should no longer `await` the result. Example: ```ts import * as find from 'empathic/find' // [!code ++] import findUp from 'find-file-up' // [!code --] await findUp('package.json', cwd) // [!code --] find.file('package.json', { cwd }) // [!code ++] ``` --- --- url: /docs/replacements/find-pkg.md description: Modern alternatives to the find-pkg package for finding package.json files --- # Replacements for `find-pkg` ## `empathic` [`empathic`](https://github.com/lukeed/empathic) provides a more generic way to find files and directories upwards. The main difference is that `empathic` is *synchronous*, so you should no longer `await` the result. Example: ```ts import * as pkg from 'empathic/package' // [!code ++] import findPkg from 'find-pkg' // [!code --] await findPkg(path) // [!code --] pkg.up(path) // [!code ++] ``` --- --- url: /docs/replacements/find-process.md description: >- Modern alternatives to the find-process package for looking up running processes --- # Replacements for `find-process` ## `find-proc` [`find-process`](https://github.com/yibn2008/find-process) pulls in 8 dependencies and 536 kB install size. [`find-proc`](https://github.com/gameroman-npm/find-proc) does the same job with no dependencies at 16 kB. In v0.1.0, `find-proc` keeps the same `find(by, value)` signature, for future breaking changes please refer to changelog in `find-proc` repo. ```ts import find from 'find-process' // [!code --] import find from 'find-proc' // [!code ++] const list = await find('port', 80) ``` --- --- url: /docs/replacements/find-up.md description: >- Modern alternatives to the find-up package for finding files by walking up parent directories --- # Replacements for `find-up` ## `empathic` [`empathic`](https://github.com/lukeed/empathic) provides a more generic way to find files and directories upwards. The main difference is that `empathic` is *synchronous*, so you should no longer `await` the result. Example: ```ts import * as find from 'empathic/find' // [!code ++] import { findUp } from 'find-up' // [!code --] await findUp('package.json') // [!code --] find.up('package.json') // [!code ++] ``` ### `findUpMultiple` When finding multiple files, you can use `find.any`: ```ts import * as find from 'empathic/find' // [!code ++] import { findUpMultiple } from 'find-up' // [!code --] const files = await findUpMultiple(['package.json', 'tsconfig.json']) // [!code --] const files = find.any(['package.json', 'tsconfig.json']) // [!code ++] ``` ### Options #### `type` The `type` option can be replaced by using the equivalent function. For example, finding a file: ```ts import * as find from 'empathic/find' // [!code ++] import { findUp } from 'find-up' // [!code --] await findUp('package.json', { type: 'file' }) // [!code --] find.file('package.json') // [!code ++] ``` #### `cwd` This option is supported just the same: ```ts find.file('package.json', { cwd }) ``` #### `stopAt` This option is replaced by `last`: ```ts import { findUp } from 'find-up' // [!code --] import * as find from 'empathic/find' // [!code ++] await findUp( // [!code --] find.file( // [!code ++] 'package.json', { stopAt: '/some/dir' }, // [!code --] { last: '/some/dir' }, // [!code ++] ) ``` ## `pkg-types` [`pkg-types`](https://github.com/unjs/pkg-types) provides utilities for reading and writing package.json, tsconfig.json, and other configuration files with TypeScript support. ```ts import { findUp } from 'find-up' // [!code --] import { readPackageJSON } from 'pkg-types' // [!code ++] const packagePath = await findUp('package.json') // [!code --] const packageJson = await readPackageJSON() // [!code ++] ``` --- --- url: /docs/replacements/fs-extra.md description: Modern alternatives to the fs-extra package for working with the file system --- # Replacements for `fs-extra` ## `fs` and `fs/promises` (native, Node.js) Modern Node.js includes built-in [`fs`](https://nodejs.org/api/fs.html) and [`fs/promises`](https://nodejs.org/api/fs.html#promises-api) APIs that cover what [`fs-extra`](https://github.com/jprichardson/node-fs-extra) historically provided. ```js import fsExtra from 'fs-extra' // [!code --] import * as fs from 'node:fs' // [!code ++] import * as fsPromises from 'node:fs/promises' // [!code ++] ``` ### `copy` ```js await fsExtra.copy(src, dest) // [!code --] await fsPromises.cp(src, dest, { recursive: true }) // [!code ++] ``` > \[!NOTE] > If src is a file and dest is an existing directory, `fs-extra` throws; `fs.cp` copies into the directory. ### `copySync` ```js fsExtra.copySync(src, dest) // [!code --] fs.cpSync(src, dest, { recursive: true }) // [!code ++] ``` ### `remove` ```js await fsExtra.remove(path) // [!code --] await fsPromises.rm(path, { recursive: true, force: true }) // [!code ++] ``` > \[!IMPORTANT] > Remember to set `{ recursive: true, force: true }` to match the behavior of `fs-extra`. ```js fsExtra.removeSync(path) // [!code --] fs.rmSync(path, { recursive: true, force: true }) // [!code ++] ``` ### `mkdirs` / `mkdirp` / `ensureDir` ```js await fsExtra.mkdirs(dir) // [!code --] await fsPromises.mkdir(dir, { recursive: true }) // [!code ++] ``` ### `mkdirsSync` / `mkdirpSync` / `ensureDirSync` ```js fsExtra.mkdirsSync(dir) // [!code --] fs.mkdirSync(dir, { recursive: true }) // [!code ++] ``` ### `pathExists` ```js await fsExtra.pathExists(path) // [!code --] await fsPromises.access(path).then(() => true, () => false) // [!code ++] ``` ### `pathExistsSync` ```js fsExtra.pathExistsSync(path) // [!code --] fs.existsSync(path) // [!code ++] ``` ### `outputFile` ```js await fsExtra.outputFile(file, data) // [!code --] await fsPromises.mkdir(path.dirname(file), { recursive: true }) // [!code ++] await fsPromises.writeFile(file, data) // [!code ++] ``` ### `outputFileSync` ```js fsExtra.outputFileSync(file, data) // [!code --] fs.mkdirSync(path.dirname(file), { recursive: true }) // [!code ++] fs.writeFileSync(file, data) // [!code ++] ``` ### `readJson` ```js await fsExtra.readJson(file) // [!code --] await fsPromises.readFile(file, 'utf8').then(JSON.parse) // [!code ++] ``` ### `writeJson` / `outputJson` ```js await fsExtra.writeJson(file, obj) // [!code --] await fsPromises.mkdir(path.dirname(file), { recursive: true }) // [!code ++] await fsPromises.writeFile(file, JSON.stringify(obj, null, 2)) // [!code ++] ``` ### `ensureFile` / `createFile` ```js await fsExtra.ensureFile(file) // [!code --] await fsPromises.mkdir(path.dirname(file), { recursive: true }) // [!code ++] await fsPromises.access(file).catch(() => fsPromises.writeFile(file, '')) // [!code ++] ``` ### `ensureFileSync` / `createFileSync` ```js fsExtra.ensureFileSync(file) // [!code --] fs.mkdirSync(path.dirname(file), { recursive: true }) // [!code ++] fs.writeFileSync(file, '') // [!code ++] ``` ### `ensureLink` / `createLink` ```js await fsExtra.ensureLink(src, dest) // [!code --] await fsPromises.mkdir(path.dirname(dest), { recursive: true }) // [!code ++] await fsPromises.link(src, dest) // [!code ++] ``` ### `ensureLinkSync` / `createLinkSync` ```js fsExtra.ensureLinkSync(src, dest) // [!code --] fs.mkdirSync(path.dirname(dest), { recursive: true }) // [!code ++] fs.linkSync(src, dest) // [!code ++] ``` ### `ensureSymlink` / `createSymlink` ```js await fsExtra.ensureSymlink(src, dest) // [!code --] await fsPromises.mkdir(path.dirname(dest), { recursive: true }) // [!code ++] await fsPromises.symlink(src, dest) // [!code ++] ``` ### `ensureSymlinkSync` / `createSymlinkSync` ```js fsExtra.ensureSymlinkSync(src, dest) // [!code --] fs.mkdirSync(path.dirname(dest), { recursive: true }) // [!code ++] fs.symlinkSync(src, dest) // [!code ++] ``` ### `emptyDir` / `emptydir` ```js await fsExtra.emptyDir(dir) // [!code --] await fsPromises.rm(dir, { recursive: true, force: true }) // [!code ++] await fsPromises.mkdir(dir, { recursive: true }) // [!code ++] ``` ### `move` ```js await fsExtra.move(src, dest) // [!code --] await fsPromises.rename(src, dest) // [!code ++] ``` > \[!NOTE] > Does not work cross-device; add `cp` + `rm` fallback. For example: ```js try { await fsPromises.rename(src, dest) } catch (err) { if (err.code === 'EXDEV') { await fsPromises.cp(src, dest, { recursive: true }) await fsPromises.rm(src, { recursive: true, force: true }) } else { throw err } } ``` --- --- url: /docs/replacements/get-stream.md description: Modern alternatives to the get-stream package --- # Replacements for `get-stream` ## Converting stream to string You can convert a stream to a string by using `Buffer.from` and a `for await`: ```ts import { Buffer } from 'node:buffer' async function streamToString(stream) { const chunks = [] for await (const chunk of stream) { chunks.push(Buffer.from(chunk)) } return Buffer.concat(chunks).toString('utf-8') } ``` ## Converting stream to Array You can convert a stream to an array using [`Array.fromAsync`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fromAsync) in Node 22+: ```ts await Array.fromAsync(stream) // or before Node 22 async function streamToArray(stream) { const chunks = [] for await (const chunk of stream) { chunks.push(chunk) } return chunks } ``` ## Converting stream to Buffer You can convert a stream to a `Buffer` using [`Array.fromAsync`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fromAsync) with a mapper: ```ts import { Buffer } from 'node:buffer' async function streamToBuffer(stream) { const chunks = await Array.fromAsync(stream, (chunk) => Buffer.from(chunk)) return Buffer.concat(chunks) } // or before Node 22 async function streamToBuffer(stream) { const chunks = [] for await (const chunk of stream) { chunks.push(Buffer.from(chunk)) } return Buffer.concat(chunks) } ``` --- --- url: /docs/replacements/glob.md description: Modern alternatives to the glob package for file pattern matching and globbing --- # Replacements for `glob` ## `tinyglobby` [`tinyglobby`](https://github.com/SuperchupuDev/tinyglobby) provides a similar API. Example: ```ts import { glob } from 'glob' // [!code --] import { glob } from 'tinyglobby' // [!code ++] const files = await glob('**/*.ts') ``` Most options available to `glob` are available in `tinyglobby`, read more at the [tinyglobby documentation](https://superchupu.dev/tinyglobby/documentation). ## `fs.glob` (native, since Node 22.x) [`fs.glob`](https://nodejs.org/api/fs.html#fspromisesglobpattern-options) is built into modern versions of Node. Example: ```ts import { glob } from 'glob' // [!code --] import { glob } from 'node:fs/promises' // [!code ++] const files = await glob('src/**/*.ts', { // [!code --] const files = await Array.fromAsync(glob('src/**/*.ts', { // [!code ++] cwd, }) // [!code --] })) // [!code ++] ``` You can also iterate over the results asynchronously: ```ts for await (const result of glob('src/**/*.ts', { cwd })) { // result is an individual path console.log(result) } ``` > \[!NOTE] > Node's built-in `glob` is more minimal and does not support negation patterns or fine-grained options like setting a max depth out of the box. ## `fdir` [`fdir`](https://github.com/thecodrr/fdir/) offers similar functionality but through a different API (and `tinyglobby` is actually built on top of it). Example: ```ts import { fdir } from 'fdir' // [!code ++] import { glob } from 'glob' // [!code --] const files = new fdir() // [!code ++] .withBasePath() // [!code ++] .glob('src/**/*.ts') // [!code ++] .crawl(cwd) // [!code ++] .withPromise() // [!code ++] const files = await glob('src/**/*.ts', { cwd, maxDepth: 6 }) // [!code --] ``` --- --- url: /docs/replacements/globby.md description: Modern alternatives to the globby package for globbing and .gitignore support --- # Replacements for `globby` ## `tinyglobby` [`globby`](https://github.com/sindresorhus/globby) is a convenience wrapper around [`fast-glob`](https://github.com/mrmlnc/fast-glob). You can follow roughly the same migration process as documented in the [`fast-glob`](./fast-glob.md) replacement guide, since `globby` is built on top of it and the main differences are its extra conveniences. If you don’t need `.gitignore` handling, prefer [`tinyglobby`](https://github.com/SuperchupuDev/tinyglobby/) - it’s smaller and faster. If you do need `.gitignore` behavior, pair `tinyglobby` with a small git-based helper. For most cases, this will likely be good enough: ```ts import { execSync } from 'node:child_process' import process from 'node:process' import { glob, escapePath } from 'tinyglobby' async function globWithGitignore(patterns, options = {}) { const { cwd = process.cwd(), ...restOptions } = options try { const gitIgnored = execSync( 'git ls-files --others --ignored --exclude-standard --directory', { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] } ) .split('\n') .filter(Boolean) .map((p) => escapePath(p)) return glob(patterns, { ...restOptions, cwd, ignore: [...(restOptions.ignore || []), ...gitIgnored] }) } catch { return glob(patterns, options) } } const paths = await globWithGitignore(['**/*'], { cwd }) ``` --- --- url: /docs/replacements/graphemer.md description: >- Modern alternatives to the grapheme-splitter and graphemer packages for splitting strings into Unicode grapheme clusters --- # Replacements for `grapheme-splitter` / `graphemer` ## `Intl.Segmenter` (native) [`Intl.Segmenter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter) is the modern native JavaScript API for text segmentation, available in Node.js 16+, Chrome 87+, Safari 14.1+, and Firefox 132+. ```ts import GraphemeSplitter from 'grapheme-splitter' // [!code --] const splitter = new GraphemeSplitter() // [!code --] const segmenter = new Intl.Segmenter() // [!code ++] const graphemes = splitter.splitGraphemes(text) // [!code --] const graphemes = [...segmenter.segment(text)].map((s) => s.segment) // [!code ++] const count = splitter.countGraphemes(text) // [!code --] const count = [...segmenter.segment(text)].length // [!code ++] ``` ## `unicode-segmenter` [`unicode-segmenter`](https://github.com/cometkim/unicode-segmenter) is a lightweight, fast alternative with zero dependencies and excellent browser compatibility. ```ts import GraphemeSplitter from 'grapheme-splitter' // [!code --] import { countGraphemes, splitGraphemes } from 'unicode-segmenter/grapheme' // [!code ++] const splitter = new GraphemeSplitter() // [!code --] const graphemes = splitter.splitGraphemes(text) // [!code --] const graphemes = [...splitGraphemes(text)] // [!code ++] const count = splitter.countGraphemes(text) // [!code --] const count = countGraphemes(text) // [!code ++] ``` You can also use it as an `Intl.Segmenter` polyfill: ```ts import 'unicode-segmenter/intl-polyfill' const segmenter = new Intl.Segmenter() const graphemes = [...segmenter.segment(text)].map((s) => s.segment) ``` --- --- url: /docs/replacements/graphviz.md description: Modern alternatives to the graphviz package --- # Replacements for `graphviz` ## `ts-graphviz` [`ts-graphviz`](https://github.com/ts-graphviz/ts-graphviz) is an actively maintained graphviz implementation in pure TypeScript. Example: ```ts import { attribute as _, Digraph, Subgraph, Node, Edge, toDot } from 'ts-graphviz' const G = new Digraph() const A = new Subgraph('A') const node1 = new Node('node1', { [_.color]: 'red' }) const node2 = new Node('node2', { [_.color]: 'blue' }) const edge = new Edge([node1, node2], { [_.label]: 'Edge Label', [_.color]: 'pink' }) G.addSubgraph(A) A.addNode(node1) A.addNode(node2) A.addEdge(edge) const dot = toDot(G) // digraph { // subgraph "A" { // "node1" [ // color = "red", // ] // "node2" [ // color = "blue", // ] // "node1" -> "node2" [ // label = "Edge Label", // color = "pink", // ] // } // } ``` --- --- url: /docs/replacements/gray-matter.md description: Modern alternatives to the gray-matter package for parsing front matter --- # Replacements for `gray-matter` `gray-matter` has not been maintained since 2019 and carries known security issues, including eval-based JavaScript front matter (RCE) and an outdated `js-yaml` dependency with DoS vulnerabilities. [`@11ty/gray-matter`](https://github.com/11ty/gray-matter) is a maintained fork used by Eleventy v4 and Docusaurus. ## `@11ty/gray-matter` [`@11ty/gray-matter`](https://github.com/11ty/gray-matter) upgrades `js-yaml` to v4, uses `Uint8Array` internally for better runtime compatibility, and removes the built-in JavaScript front matter engine that relied on `eval`. Example: ```ts import matter from 'gray-matter' // [!code --] import matter from '@11ty/gray-matter' // [!code ++] const { data, content } = matter('---\ntitle: Hello\n---\n\nBody') ``` --- --- url: /docs/replacements/gzip-size.md description: >- Native Node.js alternatives to the gzip-size for calculating gzipped size of a string or buffer --- # Replacements for `gzip-size` ## `gzipSync` (native, since Node.js 9.4.0) To calculate the gzipped size of a string or an `ArrayBuffer`, you can use [gzipSync](https://nodejs.org/api/zlib.html#zlibgzipsync) which exist inside the `zlib` module: ### Synchronous ```ts import { gzipSizeSync } from 'gzip-size' // [!code --] import { gzipSync } from 'node:zlib' // [!code ++] const text = 'Lorem ipsum dolor sil amet' console.log(gzipSizeSync(text)) // [!code --] console.log(gzipSync(text).length) // [!code ++] ``` ### Asynchronous ```ts import { gzipSize } from 'gzip-size' // [!code --] import { gzipSync } from 'node:zlib' // [!code ++] import { promisify } from 'node:util' // [!code ++] const gzipAsync = promisify(gzip) // [!code ++] const text = 'Lorem ipsum dolor sil amet' console.log(await gzipSize(text)) // [!code --] console.log((await gzipAsync(text)).length) // [!code ++] ``` ### Calculating from a file ```ts import { gzipSizeFromFileSync } from 'gzip-size' // [!code --] import { gzipSync } from 'node:zlib' // [!code ++] import { readFileSync } from 'node:fs' // [!code ++] const path = '/path/to/file' console.log(gzipSizeFromFileSync(path)) // [!code --] console.log(gzipSync(readFileSync(path)).length) // [!code ++] ``` --- --- url: /docs/replacements/has-yarn.md description: Modern alternatives to the has-yarn package for detecting a yarn.lock file --- # Replacements for `has-yarn` ## `empathic` [`empathic`](https://github.com/lukeed/empathic) provides a generic way to find files, including searching upwards through parent directories. Example: ```ts import hasYarn from 'has-yarn' // [!code --] import * as find from 'empathic/find' // [!code ++] hasYarn() // [!code --] Boolean(find.up('yarn.lock')) // [!code ++] ``` Like `has-yarn`, `find.up` walks up parent directories and accepts a starting directory: ```ts Boolean(find.up('yarn.lock', { cwd })) ``` --- --- url: /docs/replacements/html-minifier.md description: >- Modern replacement for the html-minifier package for minifying HTML with embedded CSS, JavaScript, and SVG --- # Replacements for `html-minifier` ## `html-minifier-next` [`html-minifier-next`](https://github.com/j9t/html-minifier-next) is the official successor to [`html-minifier`](https://github.com/kangax/html-minifier). It is actively maintained, faster, and adds features such as SVG minification and preset configurations. ```ts import minify from 'html-minifier' // [!code --] import { minify } from 'html-minifier-next' // [!code ++] const html = '

foo

' const result = minify(html, { collapseWhitespace: true }) // [!code --] const result = await minify(html, { collapseWhitespace: true }) // [!code ++] ``` --- --- url: /docs/replacements/http-proxy.md description: >- Modern alternatives to the http-proxy package for HTTP and WebSocket proxying in Node.js --- # Replacements for `http-proxy` [`http-proxy`](https://github.com/http-party/node-http-proxy) has not been maintained since 2020 and has known crashes, memory leaks, and socket issues on modern Node.js versions. ## `httpxy` [`httpxy`](https://github.com/unjs/httpxy) is a maintained fork of `http-proxy` with critical upstream bug fixes, zero dependencies, and additional helpers such as `proxyFetch` and `proxyUpgrade`. Example: ```ts import httpProxy from 'http-proxy' // [!code --] import { createProxyServer } from 'httpxy' // [!code ++] import { createServer } from 'node:http' const proxy = httpProxy.createProxyServer({}) // [!code --] const proxy = createProxyServer({}) // [!code ++] const server = createServer((req, res) => { proxy.web(req, res, { target: 'http://localhost:8080' }) // [!code --] proxy.web(req, res, { target: 'http://localhost:8080' }) // [!code ++] }) server.on('upgrade', (req, socket, head) => { proxy.ws(req, socket, head, { target: 'http://localhost:8080' }) // [!code --] proxy.ws(req, socket, { target: 'http://localhost:8080' }, head) // [!code ++] }) ``` ## `http-proxy-3` [`http-proxy-3`](https://github.com/sagemathinc/http-proxy-3) is a TypeScript rewrite with API compatibility, HTTP/2 support, and production use in Vite, JupyterHub, and CoCalc. Example: ```ts import httpProxy from 'http-proxy' // [!code --] import { createProxyServer } from 'http-proxy-3' // [!code ++] import { createServer } from 'node:http' const proxy = httpProxy.createProxyServer({}) // [!code --] const proxy = createProxyServer({}) // [!code ++] const server = createServer((req, res) => { proxy.web(req, res, { target: 'http://localhost:8080' }) }) server.on('upgrade', (req, socket, head) => { proxy.ws(req, socket, head, { target: 'http://localhost:8080' }) // [!code --] proxy.ws(req, socket, { target: 'http://localhost:8080' }, head) // [!code ++] }) ``` --- --- url: /docs/replacements/inherits.md description: Modern alternatives to the inherits package --- # Replacements for `inherits` ## ES6 classes `extends` syntax ES6 classes [`extends`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/extends) syntax is a native way to implement prototype inheritance. Example: ```js import EventEmitter from 'node:events' import inherits from 'inherits' // [!code --] function MyStream() { // [!code --] EventEmitter.call(this) // [!code --] } // [!code --] MyStream.prototype.write = function (data) { // [!code --] this.emit('data', data) // [!code --] } // [!code --] inherits(MyStream, EventEmitter) // [!code --] class MyStream extends EventEmitter { // [!code ++] write(data) { // [!code ++] this.emit('data', data) // [!code ++] } // [!code ++] } // [!code ++] const stream = new MyStream() stream.on('data', (data) => { console.log(`Received data: "${data}"`) }) stream.write('Hello world!') ``` ## `utils.inherits` (native, since Node.js v5.0.0) [`utils.inherits`](https://nodejs.org/docs/latest/api/util.html#utilinheritsconstructor-superconstructor) is a native legacy Node.js api. Example: ```js import inherits from 'inherits' // [!code --] import { inherits } from 'node:util' // [!code ++] inherits(Target, Base) ``` ## `Object.create` (native) [`Object.create`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create) allows you to implement inheritance. Example: ```js import inherits from 'inherits' // [!code --] inherits(Target, Base) // [!code --] Target.prototype = Object.create(Base.prototype, { // [!code ++] constructor: { // [!code ++] value: Target, // [!code ++] enumerable: false, // [!code ++] writable: true, // [!code ++] configurable: true // [!code ++] } // [!code ++] }) // [!code ++] ``` --- --- url: /docs/replacements/invariant.md description: Modern alternatives to the invariant package for runtime assertions --- # Replacements for `invariant` ## `tiny-invariant` [`tiny-invariant`](https://github.com/alexreardon/tiny-invariant) provides a similar API with zero dependencies. For example: ```ts import invariant from 'invariant' // [!code --] import invariant from 'tiny-invariant' // [!code ++] invariant(ok, 'Hello %s, code %d', name, code) // [!code --] invariant(ok, `Hello ${name}, code ${code}`) // [!code ++] ``` Similarly, you can lazily compute messages to avoid unnecessary work: ```ts import invariant from 'invariant' // [!code --] import invariant from 'tiny-invariant' // [!code ++] invariant(value, getExpensiveMessage()) // [!code --] invariant(value, () => getExpensiveMessage()) // [!code ++] ``` --- --- url: /docs/replacements/is-builtin-module.md description: >- Native Node.js alternatives to the is-builtin-module package for checking built-in modules --- # Replacements for `is-builtin-module` ## `isBuiltin` (native, since Node.js 16.x) For determining if a module is built-in or not, you can use [isBuiltin](https://nodejs.org/api/module.html#moduleisbuiltinmodulename): ```ts import { isBuiltin } from 'node:module' // [!code ++] import isBuiltinModule from 'is-builtin-module' // [!code --] isBuiltin('fs') // true [!code ++] isBuiltinModule('fs') // true [!code --] ``` ## `builtInModules` (native, since Node.js 6.x and 15.x) Before Node.js 16.x, `isBuiltin` was not available, so you need to implement your own check using [builtinModules](https://nodejs.org/api/module.html#modulebuiltinmodules): ```ts import { builtinModules } from 'node:module' // [!code ++] import isBuiltinModule from 'is-builtin-module' // [!code --] function isBuiltin(moduleName) { // [!code ++] const name = moduleName.startsWith('node:') // [!code ++] ? moduleName.slice(5) // [!code ++] : moduleName // [!code ++] return builtinModules.includes(name) // [!code ++] } // [!code ++] isBuiltin('fs') // true [!code ++] isBuiltinModule('fs') // true [!code --] ``` --- --- url: /docs/replacements/is-path-in-cwd.md description: >- Native alternatives to the is-path-in-cwd package for checking whether a path is inside the current working directory --- # Replacements for `is-path-in-cwd` `is-path-in-cwd` is `is-path-inside` with `process.cwd()` as the parent, so use the [`is-path-inside` replacement](./is-path-inside.md) and pass the working directory yourself. ```ts import path from 'node:path' import process from 'node:process' const isPathInside = (childPath, parentPath) => { const relation = path.relative(parentPath, childPath) return Boolean( relation && relation !== '..' && !relation.startsWith(`..${path.sep}`) && relation !== path.resolve(childPath) ) } const isPathInCwd = (p) => isPathInside(p, process.cwd()) ``` --- --- url: /docs/replacements/is-path-inside.md description: >- Native alternatives to the is-path-inside package for checking whether one path is inside another --- # Replacements for `is-path-inside` [`path.relative`](https://nodejs.org/api/path.html#pathrelativefrom-to) gives you the route from the parent to the child. If that route is empty, or climbs out with `..`, the child is not inside the parent. The comparison against `path.resolve` catches paths on a different Windows drive, where `path.relative` returns an absolute path rather than one starting with `..`. ```ts import isPathInside from 'is-path-inside' // [!code --] import path from 'node:path' // [!code ++] const isPathInside = (childPath, parentPath) => { // [!code ++] const relation = path.relative(parentPath, childPath) // [!code ++] return Boolean( // [!code ++] relation && // [!code ++] relation !== '..' && // [!code ++] !relation.startsWith(`..${path.sep}`) && // [!code ++] relation !== path.resolve(childPath) // [!code ++] ) // [!code ++] } // [!code ++] ``` Note that this is purely a string comparison. Neither the package nor the replacement touches the filesystem, so symlinks are not resolved. --- --- url: /docs/replacements/is-utf8.md description: >- Native alternatives to the is-utf8 package for checking whether bytes are valid UTF-8 --- # Replacements for `is-utf8` Both Node.js and the web platform can check for valid UTF-8 without a dependency. The two options below return the same answer, so it is only a question of where the code runs. One difference to keep in mind: among control characters `is-utf8` accepts only tab, line feed and carriage return, and rejects the other thirty even though all of them are valid UTF-8. If you were relying on that to tell text from binary, keep the check explicit. ## `buffer.isUtf8` (native, since Node.js v19.4.0 / v18.14.0) [`buffer.isUtf8`](https://nodejs.org/api/buffer.html#bufferisutf8input) takes a `Buffer`, `TypedArray` or `ArrayBuffer`. ```ts import isUtf8 from 'is-utf8' // [!code --] import { isUtf8 } from 'node:buffer' // [!code ++] isUtf8(bytes) ``` ## `TextDecoder` (native, browsers and Node.js) Elsewhere, decode with [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) and `fatal: true`, which throws on malformed input instead of substituting replacement characters. ```ts import isUtf8 from 'is-utf8' // [!code --] const decoder = new TextDecoder('utf-8', {fatal: true}) // [!code ++] const isUtf8 = (bytes) => { // [!code ++] try { // [!code ++] decoder.decode(bytes) // [!code ++] return true // [!code ++] } catch { // [!code ++] return false // [!code ++] } // [!code ++] } // [!code ++] ``` --- --- url: /docs/replacements/istanbul-lib-coverage.md description: Modern alternatives to the istanbul-lib-coverage package --- # Replacements for `istanbul-lib-coverage` `istanbul-lib-coverage` is no longer maintained. ## `@vitest/istanbul-lib-coverage` The Vitest team publishes [`@vitest/istanbul-lib-coverage`](https://www.npmjs.com/package/@vitest/istanbul-lib-coverage), a maintained fork that keeps the same API. The exported functions and classes keep their existing names, so in most projects the only change is the package you import from. ```ts import { createCoverageMap } from 'istanbul-lib-coverage' // [!code --] import { createCoverageMap } from '@vitest/istanbul-lib-coverage' // [!code ++] const map = createCoverageMap(coverageData) ``` Compared to the original it is: * Actively maintained * Shipped as ESM instead of CommonJS only * Bundled with its own type definitions, so you no longer need `@types/istanbul-lib-coverage` > \[!NOTE] > The fork is ESM-only and sets `engines.node` to `>=22`, so switching is a > larger jump than a plain import swap for projects still on CommonJS or an > older Node version. If you also depend on `@types/istanbul-lib-coverage` only for this package, you can drop it once you migrate, since the fork ships its own types. --- --- url: /docs/replacements/istanbul-lib-instrument.md description: >- Modern alternatives to the istanbul-lib-instrument package for instrumenting code for coverage --- # Replacements for `istanbul-lib-instrument` ## `@vitest/istanbul-lib-instrument` [`istanbul-lib-instrument`](https://github.com/istanbuljs/istanbuljs) is unmaintained. [`@vitest/istanbul-lib-instrument`](https://github.com/vitest-dev/istanbuljs) is a maintained fork from the Vitest team that keeps the same exports, so in most cases only the import needs to change: ```ts import { createInstrumenter } from 'istanbul-lib-instrument' // [!code --] import { createInstrumenter } from '@vitest/istanbul-lib-instrument' // [!code ++] ``` > \[!NOTE] > The fork is ESM-only and sets `engines.node` to `>=22`, so switching is a > larger jump than a plain import swap for projects still on CommonJS or an > older Node version. If you also depend on `@types/istanbul-lib-instrument` only for this package, you can drop it once you migrate, since the fork ships its own types. --- --- url: /docs/replacements/jquery.md description: Modern alternatives to the jQuery library for DOM traversal, events, and AJAX --- # Replacements for `jQuery` ## You might not need jQuery [You might not need jQuery](https://youmightnotneedjquery.com/) is a side‑by‑side catalog of native JavaScript equivalents for common jQuery patterns (selectors, traversal, manipulation, events, AJAX), with concise before/after examples. ## You (Might) Don't Need jQuery [You‑Dont‑Need‑jQuery](https://github.com/camsong/You-Dont-Need-jQuery) is a community‑maintained guide that shows how to handle querying, styling, DOM manipulation, AJAX, and events with plain JavaScript. --- --- url: /docs/replacements/js-yaml.md description: Modern alternatives to js-yaml for YAML parsing and stringifying --- # Replacements for `js-yaml` The alternatives below can offer better spec compliance, smaller install sizes. ## `yaml` [`yaml`](https://github.com/eemeli/yaml) is a well maintained YAML 1.2/1.1 parser/stringifier with better spec compliance, comment/AST support, and no deps. Parse (load): ```ts import yaml from 'js-yaml' // [!code --] import { parse } from 'yaml' // [!code ++] const obj = yaml.load(src) // [!code --] const obj = parse(src) // [!code ++] ``` Stringify (dump): ```ts import yaml from 'js-yaml' // [!code --] import { stringify } from 'yaml' // [!code ++] const text = yaml.dump(obj) // [!code --] const text = stringify(obj) // [!code ++] ``` Multi-document: ```ts import yaml from 'js-yaml' // [!code --] import { parseAllDocuments } from 'yaml' // [!code ++] const out: any[] = [] // [!code --] yaml.loadAll(src, (d) => out.push(d)) // [!code --] const out = parseAllDocuments(src).map((d) => d.toJSON()) // [!code ++] ``` ## Bun `YAML` API [Native YAML parsing](https://bun.com/docs/runtime/yaml) is supported in Bun since [v1.2.21](https://bun.com/blog/release-notes/bun-v1.2.21#native-yaml-support). Example: ```ts import yaml from 'js-yaml' // [!code --] import { YAML } from 'bun' // [!code ++] yaml.load(src) // [!code --] YAML.parse(src) // [!code ++] ``` --- --- url: /docs/replacements/jsonwebtoken.md description: >- Modern alternatives to the jsonwebtoken package for JWT signing and verification --- # Replacements for `jsonwebtoken` ## `jose` [`jose`](https://github.com/panva/jose) implements the full JOSE standard (JWK, JWS, JWE, JWT, JWKS) using the Web Crypto API Compared to `jsonwebtoken`, it works everywhere globalThis.crypto is available: Node.js 18+, Deno, Bun, Cloudflare Workers, and browsers. ```ts import jwt from 'jsonwebtoken' // [!code --] import { SignJWT, jwtVerify } from 'jose' // [!code ++] const secret = 'mysecretkey' const payload = { userId: 123 } const token = jwt.sign(payload, secret, { expiresIn: '1h' }) // [!code --] const decoded = jwt.verify(token, secret) // [!code --] const joseSecret = new TextEncoder().encode(secret) // [!code ++] const token = await new SignJWT(payload) // [!code ++] .setProtectedHeader({ alg: 'HS256' }) // [!code ++] .setExpirationTime('1h') // [!code ++] .sign(joseSecret) // [!code ++] const { payload: decoded } = await jwtVerify(token, joseSecret) // [!code ++] ``` --- --- url: /docs/replacements/jsx-ast-utils.md description: >- Modern alternatives to the jsx-ast-utils package for statically analyzing JSX ASTs --- # Replacements for `jsx-ast-utils` ## `jsx-ast-utils-x` [`jsx-ast-utils-x`](https://github.com/eslinter/jsx-ast-utils-x) is a zero‑dependency alternative to [`jsx-ast-utils`](https://github.com/jsx-eslint/jsx-ast-utils) that aims to maintain API compatibility while reducing package size. ```ts import { hasProp } from 'jsx-ast-utils' // [!code --] import { hasProp } from 'jsx-ast-utils-x' // [!code ++] import hasProp from 'jsx-ast-utils/hasProp' // [!code --] import hasProp from 'jsx-ast-utils-x/hasProp' // [!code ++] module.exports = (context) => ({ JSXOpeningElement: (node) => { const onChange = hasProp(node.attributes, 'onChange') if (onChange) { context.report({ node, message: 'No onChange!' }) } } }) ``` --- --- url: /docs/replacements/jszip.md description: Modern alternatives to the jszip package for creating and reading ZIP files --- # Replacements for `jszip` `jszip` creates and reads ZIP archives. Its dependencies are dated: it still pulls in `pako` 1, `readable-stream` 2 and the `lie` (Promise) and `setimmediate` polyfills. ## `fflate` [`fflate`](https://github.com/101arrowz/fflate) has no dependencies and can create and read ZIP archives synchronously or asynchronously. Instead of a `JSZip` object, an archive is a plain object that maps file names to `Uint8Array` contents. Nested objects become folders. Creating an archive: ```ts import JSZip from 'jszip' // [!code --] import { zipSync, strToU8 } from 'fflate' // [!code ++] const zip = new JSZip() // [!code --] zip.file('hello.txt', 'Hello World') // [!code --] zip.folder('images').file('pixel.png', pixel) // [!code --] const bytes = await zip.generateAsync({ type: 'uint8array' }) // [!code --] const hello = strToU8('Hello World') // [!code ++] const bytes = zipSync({ 'hello.txt': hello, images: { 'pixel.png': pixel } }) // [!code ++] ``` Reading an archive: ```ts import JSZip from 'jszip' // [!code --] import { unzipSync, strFromU8 } from 'fflate' // [!code ++] const zip = await JSZip.loadAsync(bytes) // [!code --] const text = await zip.file('hello.txt').async('string') // [!code --] const files = unzipSync(bytes) // [!code ++] const text = strFromU8(files['hello.txt']) // [!code ++] ``` Folder entries show up in `unzipSync` results as empty files whose names end in `/`, like `jszip`'s `files` keys. To extract only some files, pass a `filter` option, for example `unzipSync(bytes, { filter: (file) => file.name.endsWith('.txt') })`. `jszip` stores files without compression unless you pass `compression: 'DEFLATE'`, while `fflate` compresses at level 6 by default. Pass `{ level: 0 }` to store files uncompressed. `zip` and `unzip` are the asynchronous versions. They take a callback and run the work in a Web or Node worker, which adds some startup overhead, so they pay off for larger archives. For `type: 'blob'` or `type: 'nodebuffer'` output, wrap the bytes with `new Blob([bytes])` or `Buffer.from(bytes)`. --- --- url: /docs/replacements/materialize-css.md description: >- Modern alternatives to materialize-css (Materialize) and modern Material Design UI libraries --- # Replacements for `materialize-css` ## `@materializecss/materialize` [`@materializecss/materialize`](https://github.com/materializecss/materialize) is a community-maintained fork of [`Materialize`](https://github.com/Dogfalo/materialize). Acts as a practical replacement for the original materialize-css package. ## `@material/web` > \[!NOTE] > The project is currently in maintenance mode pending new maintainers. Modern Web Components implementing Material Design 3. [Project Page](https://github.com/material-components/material-web) --- --- url: /docs/replacements/md5.md description: Native Node.js alternatives to the md5 package for MD5 hash generation --- # Replacements for `md5` ## `crypto` (native) If you're using the [`md5`](https://github.com/pvorb/node-md5) package, *strongly* consider moving to a stronger algorithm if your usage is security-sensitive (for example, passwords). MD5 is [not secure](https://en.wikipedia.org/wiki/MD5#Security) and hasn't been secure for many years. If you must keep MD5 for compatibility, Node.js provides a native alternative via the [`crypto` module](https://nodejs.org/api/crypto.html#crypto). ```ts import crypto from 'node:crypto' // [!code ++] import md5 from 'md5' // [!code --] md5('message') // [!code --] crypto.createHash('md5').update('message').digest('hex') // [!code ++] ``` --- --- url: /docs/replacements/micromatch.md description: Modern alternatives to the micromatch package for glob pattern matching --- # Replacements for `micromatch` ## `path.matchesGlob` (native, since Node 20.17 / 22.5) [`path.matchesGlob`](https://nodejs.org/api/path.html#pathmatchesglobpath-pattern-options) is built into modern versions of Node. Use it when you only need to test whether a path matches a single glob pattern. Example: ```ts import micromatch from 'micromatch' // [!code --] import path from 'node:path' // [!code ++] micromatch.isMatch('foo.bar', '*.bar') // [!code --] path.matchesGlob('foo.bar', '*.bar') // [!code ++] ``` For multiple patterns, test each pattern explicitly: ```ts const patterns = ['*.js', '*.ts'] const matches = patterns.some((pattern) => path.matchesGlob(file, pattern)) ``` ## `picomatch` [`picomatch`](https://github.com/micromatch/picomatch) is the matcher micromatch is built on. It is a strong drop-in for most `isMatch` and list-filtering use cases. Example: ```ts import micromatch from 'micromatch' // [!code --] import picomatch from 'picomatch' // [!code ++] micromatch.isMatch('foo.bar', '*.bar') // [!code --] picomatch.isMatch('foo.bar', '*.bar') // [!code ++] ``` For a reusable matcher function: ```ts const isMatch = picomatch('*.js') isMatch('a.js') // true ``` ## `zeptomatch` [`zeptomatch`](https://github.com/fabiospampinato/zeptomatch) is a tiny glob matcher with brace expansion, ranges, and negation. Note that its argument order is **glob first, path second**. Example: ```ts import micromatch from 'micromatch' // [!code --] import zeptomatch from 'zeptomatch' // [!code ++] micromatch.isMatch('foo.bar', '*.bar') // [!code --] zeptomatch('*.bar', 'foo.bar') // [!code ++] ``` Zeptomatch also supports partial matching for filesystem walks and brace expansion: ```ts zeptomatch('foo/bar/*.js', 'foo', { partial: true }) // true zeptomatch('{a,b}.js', 'a.js') // true zeptomatch('!*.test.js', 'foo.js') // true ``` --- --- url: /docs/replacements/mime.md description: >- Lighter alternative to the mime package for looking up MIME types from file extensions --- # Replacements for `mime` ## `mrmime` [`mrmime`](https://github.com/lukeed/mrmime) is a smaller alternative to [`mime`](https://github.com/broofa/mime) for looking up MIME types from extensions or filenames. It only includes standard MIME types; experimental and vendor-specific types are omitted. Example: ```js import mime from 'mime' // [!code --] import { lookup } from 'mrmime' // [!code ++] mime.getType('txt') // [!code --] lookup('txt') // [!code ++] // also works: lookup('.txt'), lookup('a.txt') ``` --- --- url: /docs/replacements/mississippi.md description: Modern alternatives to the mississippi package --- # Replacements for `mississippi` `mississippi` exposes stream helpers as `miss.pipe`, `miss.each`, `miss.pipeline`, and so on. Each export wraps a separate npm package internally, but migration targets the **`miss.*` call sites** and replaces each with the corresponding native `node:stream` API below. ## `miss.pipe` ```ts import miss from 'mississippi' // [!code --] import { pipeline } from 'node:stream/promises' // [!code ++] miss.pipe(s1, s2, s3, cb) // [!code --] await pipeline(s1, s2, s3) // [!code ++] ``` For callback-style usage, use [`stream.pipeline`](https://nodejs.org/api/stream.html#streampipelinestreams-callback). ## `miss.each` ```ts import miss from 'mississippi' // [!code --] miss.each(stream, (data, cb) => { // [!code --] fn(data) // [!code --] cb() // [!code --] }) // [!code --] for await (const data of stream) { // [!code ++] fn(data) // [!code ++] } // [!code ++] ``` Note that `miss.each` reports stream errors through its done callback, while `for await` throws them wrap the loop in `try/catch` to handle errors. ## `miss.pipeline` ```ts import miss from 'mississippi' // [!code --] import { compose } from 'node:stream' // [!code ++] miss.pipeline(s1, s2, s3) // [!code --] compose(s1, s2, s3) // [!code ++] ``` Requires Node.js ≥ 16.9.0. ## `miss.duplex` See also [duplexer replacements](./duplexer.md). ```ts import miss from 'mississippi' // [!code --] import { Duplex } from 'node:stream' // [!code ++] miss.duplex(writableStream, readableStream) // [!code --] Duplex.from({ // [!code ++] writable: writableStream, // [!code ++] readable: readableStream // [!code ++] }) // [!code ++] ``` Requires Node.js ≥ 16.8.0 for `Duplex.from`. ## `miss.through` See also [through replacements](./through.md). ```ts import miss from 'mississippi' // [!code --] import { Transform } from 'node:stream' // [!code ++] miss.through((chunk, enc, cb) => { // [!code --] cb(null, fn(chunk)) // [!code --] }) // [!code --] new Transform({ // [!code ++] transform(chunk, encoding, callback) { // [!code ++] callback(null, fn(chunk)) // [!code ++] } // [!code ++] }) // [!code ++] ``` ## `miss.concat` See also [get-stream replacements](./get-stream.md). ```ts import miss from 'mississippi' // [!code --] import { buffer } from 'node:stream/consumers' // [!code ++] stream.pipe(miss.concat((data) => fn(data))) // [!code --] const data = await buffer(stream) // [!code ++] fn(data) // [!code ++] ``` Unlike `miss.concat`, which adapts its output to the input type, `buffer` always returns a `Buffer`. `node:stream/consumers` also exports `text`, `json`, and `arrayBuffer` for those cases. Requires Node.js ≥ 16.7.0 for `node:stream/consumers`. ## `miss.finished` ```ts import miss from 'mississippi' // [!code --] import { finished } from 'node:stream/promises' // [!code ++] miss.finished(stream, cb) // [!code --] await finished(stream) // [!code ++] ``` For callback-style usage, use [`stream.finished`](https://nodejs.org/api/stream.html#streamfinishedstream-options-callback). ## `miss.from` ```ts import miss from 'mississippi' // [!code --] import { Readable } from 'node:stream' // [!code ++] miss.from((size, cb) => cb(null, chunk)) // [!code --] Readable.from( // [!code ++] (async function* () { // [!code ++] yield chunk // [!code ++] })() // [!code ++] ) // [!code ++] ``` ## `miss.to` ```ts import miss from 'mississippi' // [!code --] import { Writable } from 'node:stream' // [!code ++] miss.to( // [!code --] (chunk, enc, cb) => cb(), // [!code --] (cb) => cb() // [!code --] ) // [!code --] new Writable({ // [!code ++] write(chunk, encoding, callback) { // [!code ++] callback() // [!code ++] }, // [!code ++] final(callback) { // [!code ++] callback() // [!code ++] } // [!code ++] }) // [!code ++] ``` ## `miss.parallel` ```ts import miss from 'mississippi' // [!code --] stream.pipe( // [!code --] miss.parallel(10, (data, cb) => cb(null, fn(data))) // [!code --] ) // [!code --] stream.map((data) => fn(data), { concurrency: 10 }) // [!code ++] ``` Like `miss.parallel`, `map` preserves the order of the input stream. Requires Node.js >= 17.4.0 for `Readable.prototype.map`. --- --- url: /docs/replacements/mkdirp.md description: >- Modern alternatives to the mkdirp and make-dir packages for recursively creating directories in Node.js --- # Replacements for `mkdirp` / `make-dir` ## Recursive `fs.mkdir` (native, since Node.js v10.12.0) Node.js v10.12.0 and up supports the `recursive` option in the [`fs.mkdir`](https://nodejs.org/api/fs.html#fsmkdirpath-options-callback) function, which allows parent directories to be created automatically. Example migration from [`mkdirp`](https://github.com/isaacs/node-mkdirp): ```ts import { mkdirp } from 'mkdirp' // [!code --] import { mkdir, mkdirSync } from 'node:fs' // [!code ++] import { mkdir as mkdirAsync } from 'node:fs/promises' // [!code ++] // Async await mkdirp('/tmp/foo/bar/baz') // [!code --] await mkdirAsync('/tmp/foo/bar/baz', { recursive: true }) // [!code ++] // Sync mkdirp.sync('/tmp/foo/bar/baz') // [!code --] mkdirSync('/tmp/foo/bar/baz', { recursive: true }) // [!code ++] ``` Example migration from [`make-dir`](https://github.com/sindresorhus/make-dir): ```ts import { makeDirectory, makeDirectorySync } from 'make-dir' // [!code --] import { mkdir, mkdirSync } from 'node:fs' // [!code ++] import { mkdir as mkdirAsync } from 'node:fs/promises' // [!code ++] // Async await makeDirectory('/tmp/foo/bar/baz') // [!code --] await mkdirAsync('/tmp/foo/bar/baz', { recursive: true }) // [!code ++] // Sync makeDirectorySync('/tmp/foo/bar/baz') // [!code --] mkdirSync('/tmp/foo/bar/baz', { recursive: true }) // [!code ++] ``` --- --- url: /docs/replacements/mockdate.md description: Modern alternatives to the mockdate package for mocking time in tests --- # Replacements for `mockdate` `mockdate` is mainly used in tests, and modern test runners already include built-in APIs for mocking time without pulling in an extra dependency. ## `vitest` [`vitest`](https://vitest.dev/guide/mocking.html#mock-the-current-date) provides `vi.useFakeTimers()` and `vi.setSystemTime()` for mocking the current date during tests. ```ts import MockDate from 'mockdate' // [!code --] import { vi, test, expect } from 'vitest' // [!code ++] test('freeze date', () => { MockDate.set('2026-01-01') // [!code --] vi.useFakeTimers() // [!code ++] vi.setSystemTime(new Date('2026-01-01')) // [!code ++] expect(new Date().toISOString()).toBe('2026-01-01T00:00:00.000Z') MockDate.reset() // [!code --] vi.useRealTimers() // [!code ++] }) ``` ## `node:test` [`node:test`](https://nodejs.org/en/learn/test-runner/mocking#time) supports mocking time via `mock.timers` since node 20.4.0 and later. ```ts import MockDate from 'mockdate' // [!code --] import { test } from 'node:test' // [!code ++] import assert from 'node:assert/strict' // [!code ++] test('freeze date', (t) => { MockDate.set('2026-01-01') // [!code --] t.mock.timers.enable({ apis: ['Date'], now: new Date('2026-01-01') }) // [!code ++] assert.equal(new Date().toISOString(), '2026-01-01T00:00:00.000Z') MockDate.reset() // [!code --] }) ``` ## `bun:test` [`bun:test`](https://bun.com/docs/guides/test/mock-clock) provides `mock.timers.enable()` for mocking time in tests. ```ts import MockDate from 'mockdate' // [!code --] import { test, expect, mock } from 'bun:test' // [!code ++] test('freeze date', () => { MockDate.set('2026-01-01') // [!code --] mock.timers.enable({ now: new Date('2026-01-01') }) // [!code ++] expect(new Date().toISOString()).toBe('2026-01-01T00:00:00.000Z') MockDate.reset() // [!code --] mock.timers.reset() // [!code ++] }) ``` --- --- url: /docs/replacements/moment.md description: Modern alternatives to moment.js for date manipulation and formatting --- # Replacements for `Moment.js` ## `day.js` [Day.js](https://github.com/iamkun/dayjs/) provides a similar API to Moment.js with a much smaller footprint. Example: ```ts import moment from 'moment' // [!code --] import dayjs from 'dayjs' // [!code ++] const now = moment() // [!code --] const now = dayjs() // [!code ++] const formatted = moment().format('YYYY-MM-DD') // [!code --] const formatted = dayjs().format('YYYY-MM-DD') // [!code ++] ``` ## `date-fns` [date-fns](https://github.com/date-fns/date-fns) offers tree-shakable functions for working with native JavaScript dates. Example: ```ts import moment from 'moment' // [!code --] import { addDays, format, subWeeks } from 'date-fns' // [!code ++] const formatted = moment().format('YYYY-MM-DD') // [!code --] const formatted = format(new Date(), 'yyyy-MM-dd') // [!code ++] const tomorrow = moment().add(1, 'day') // [!code --] const tomorrow = addDays(new Date(), 1) // [!code ++] const lastWeek = moment().subtract(1, 'week') // [!code --] const lastWeek = subWeeks(new Date(), 1) // [!code ++] ``` ## `luxon` [Luxon](https://github.com/moment/luxon) is created by a Moment.js maintainer and offers powerful internationalization support. Example: ```ts import moment from 'moment' // [!code --] import { DateTime } from 'luxon' // [!code ++] const now = moment() // [!code --] const now = DateTime.now() // [!code ++] const formatted = moment().format('YYYY-MM-DD') // [!code --] const formatted = DateTime.now().toFormat('yyyy-MM-dd') // [!code ++] const tomorrow = moment().add(1, 'day') // [!code --] const tomorrow = DateTime.now().plus({ days: 1 }) // [!code ++] ``` ## `Date` (native) For simple use cases, native JavaScript [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) and [`Intl`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) APIs may be sufficient: ```ts import moment from 'moment' // [!code --] const formatted = moment().format('YYYY-MM-DD') // [!code --] const formatted = new Date().toISOString().split('T')[0] // [!code ++] const localized = moment().format('MMMM Do YYYY') // [!code --] const localized = new Intl.DateTimeFormat('en-US', { // [!code ++] year: 'numeric', // [!code ++] month: 'long', // [!code ++] day: 'numeric' // [!code ++] }).format(new Date()) // [!code ++] ``` ## `Temporal` (native) The [`Temporal`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal) API is a modern, native JavaScript specification that replaces the legacy `Date` object and removes the need for external libraries like Moment.js by providing immutable date-time objects and built-in timezone handling. Example: ```ts import moment from 'moment' // [!code --] const now = moment() // [!code --] const now = Temporal.Now.instant() // [!code ++] const formatted = moment().format('YYYY-MM-DD') // [!code --] const formatted = Temporal.Now.plainDateISO().toString() // [!code ++] const tomorrow = moment().add(1, 'day') // [!code --] const tomorrow = Temporal.Now.plainDateISO().add({ days: 1 }) // [!code ++] ``` --- --- url: /docs/replacements/node-telegram-bot-api.md description: Modern alternatives to the node-telegram-bot-api package --- # Replacements for `node-telegram-bot-api` ## `grammy` [`grammy`](https://grammy.dev) is a modern library for creating telegram bots. Example: ```ts import process from 'node:process' import { Bot } from 'grammy' const bot = new Bot(process.env.TOKEN) bot.on('message', (ctx) => ctx.reply('Hi there!')) bot.start() ``` --- --- url: /docs/replacements/npm-check.md description: >- Replacements for the npm-check package, for checking unused and outdated dependencies. --- # Replacements for `npm-check` ## Package manager built-in commands Modern package managers cover checking for and applying dependency updates out of the box, with no extra dependency. ### npm ```sh npm outdated npm update ``` ### pnpm ```sh pnpm outdated pnpm update --interactive --latest ``` ### yarn ```sh yarn upgrade-interactive ``` ### bun ```sh bun outdated bun update --interactive ``` In monorepos, use `--recursive` to cover all workspaces, or `--filter` to target specific ones: ```sh bun outdated --recursive bun update --interactive --filter '@myorg/*' ``` ## `taze` If you want more than the built-ins offer (e.g. monorepo-wide updates, grouped output, update modes), [`taze`](https://github.com/antfu-collective/taze) is a modern, smaller alternative to `npm-check`. ```sh # check for updates npx taze # write updates to package.json npx taze -w # include major version bumps npx taze major -w ``` > \[!NOTE] > `npm-check`'s unused-dependency detection is not covered by these commands. You can use [`knip`](https://knip.dev) for that instead. --- --- url: /docs/replacements/npm-run-all.md description: >- Modern alternatives to the npm-run-all package for running multiple npm scripts --- # Replacements for `npm-run-all` ## `npm-run-all2` [npm-run-all2](https://github.com/bcomnes/npm-run-all2) is an actively maintained fork with important fixes, dependency updates. ```json { "scripts": { "build": "npm-run-all clean lint compile" } } ``` The commands remain the same: `npm-run-all`, `run-s`, and `run-p`. ## `concurrently` Another option is [concurrently](https://github.com/open-cli-tools/concurrently), which focuses on running scripts in parallel with colored output and process control. It uses a slightly different syntax but works well for replacing the `--parallel` use case. ```json { "scripts": { "dev": "npm-run-all --parallel \"watch-*\" start", // [!code --] "dev": "concurrently \"npm:watch-*\" \"npm:start\"" // [!code ++] } } ``` ## `wireit` For more advanced workflows, consider [Wireit](https://github.com/google/wireit). It integrates directly into `package.json` to add caching, dependency graphs, watch mode, and incremental builds. Unlike `npm-run-all`, Wireit upgrades your existing `npm run` experience instead of providing a separate CLI. ```json { "scripts": { "build": "wireit", "compile": "wireit", "bundle": "wireit" }, "wireit": { "build": { "dependencies": ["compile", "bundle"] }, "compile": { "command": "tsc", "files": ["src/**/*.ts"], "output": ["lib/**"] }, "bundle": { "command": "rollup -c", "dependencies": ["compile"], "files": ["rollup.config.js"], "output": ["dist/**"] } } } ``` ## `bun run --parallel` / `bun run --sequential` If you are using bun you can use [`bun run --parallel`](https://bun.com/docs/runtime#param-parallel) and [`bun run --sequential`](https://bun.com/docs/runtime#param-sequential). ### Parallel ```json { "scripts": { "dev": "npm-run-all --parallel \"dev:*\"", // [!code --] "dev": "bun run --parallel dev:*" // [!code ++] } } ``` ### Sequential ```json { "scripts": { "build": "run-s clean lint compile", // [!code --] "build": "bun run --sequential clean lint compile" // [!code ++] } } ``` ## `pnpm run "//"` If you are using pnpm you can use [regex script matching](https://pnpm.io/cli/run#running-multiple-scripts) to run multiple scripts at once. Wrap the regex in quotes so the shell does not mangle it. ```json { "scripts": { "dev": "npm-run-all --parallel \"dev:*\"", // [!code --] "dev": "pnpm run \"/^dev:.*/\"" // [!code ++] } } ``` To run them sequentially instead of in parallel, add [`--sequential`](https://pnpm.io/cli/run#--sequential--s): ```json { "scripts": { "build": "pnpm run --sequential \"/^build:.*/\"" } } ``` --- --- url: /docs/replacements/object-hash.md description: Modern alternatives to object-hash for hashing objects and values --- # Replacements for `object-hash` ## `ohash` [`ohash`](https://github.com/unjs/ohash) is actively maintained and provides hashing, stable serialization, equality checks, and diffs. It uses stable serialization + SHA-256, returning Base64URL by default. Its serializer was originally based on `object-hash`. Example: ```ts import objectHash from 'object-hash' // [!code --] import { hash } from 'ohash' // [!code ++] const h = objectHash(obj) // [!code --] const h = hash(obj) // [!code ++] ``` ## Web Crypto Use the standard [`SubtleCrypto.digest`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest) available in modern runtimes. Pair it with a stable serializer (e.g., [`safe-stable-stringify`](https://github.com/BridgeAR/safe-stable-stringify)) to ensure deterministic key ordering. Example: ```ts import objectHash from 'object-hash' // [!code --] import stringify from 'safe-stable-stringify' // [!code ++] const h = objectHash(obj, { algorithm: 'sha256' }) // [!code --] const data = new TextEncoder().encode(stringify(obj)) // [!code ++] const buf = await crypto.subtle.digest('SHA-256', data) // [!code ++] const h = Array.from(new Uint8Array(buf)) // [!code ++] .map((b) => b.toString(16).padStart(2, '0')) // [!code ++] .join('') // [!code ++] ``` ## Bun `CryptoHasher` Bun provides a native incremental hasher (e.g., SHA-256). Combine it with a stable serializer for object hashing. For fast non-crypto fingerprints, see [`Bun.hash`](https://bun.com/reference/bun/hash). Docs: https://bun.com/reference/bun/CryptoHasher Example: ```ts import objectHash from 'object-hash' // [!code --] import stringify from 'safe-stable-stringify' // [!code ++] const h = objectHash(obj, { algorithm: 'sha256' }) // [!code --] const hasher = new CryptoHasher('sha256') // [!code ++] hasher.update(stringify(obj)) // [!code ++] const h = hasher.digest('hex') // [!code ++] ``` --- --- url: /docs/replacements/open.md description: Smaller alternatives to the open package for opening files and URLs --- # Replacements for `open` ## `tiny-open` [`tiny-open`](https://github.com/fabiospampinato/tiny-open) is a tiny, zero-dependency utility for opening a file or URL with its default application. It covers the common use cases of [`open`](https://github.com/sindresorhus/open) with a much smaller install footprint. Example: ```ts import open from 'open' // [!code --] import open from 'tiny-open' // [!code ++] await open('https://example.com') await open('/path/to/file.txt') // with a specific app await open('https://example.com', 'Google Chrome') ``` > \[!NOTE] > Advanced options (`wait`, `app.arguments`, the `openApp` export) have no direct equivalent in `tiny-open`. --- --- url: /docs/replacements/ora.md description: >- Modern alternatives to the ora package for displaying elegant terminal spinners with status indicators --- # Replacements for `ora` ## `nanospinner` [`nanospinner`](https://github.com/usmanyunusov/nanospinner) provides simple start/success/error/warning methods with one dependency (`picocolors`). ```ts import ora from 'ora' // [!code --] import { createSpinner } from 'nanospinner' // [!code ++] const spinner = ora('Loading...').start() // [!code --] const spinner = createSpinner('Loading...').start() // [!code ++] spinner.succeed('Done!') // [!code --] spinner.success('Done!') // [!code ++] spinner.fail('Error!') // [!code --] spinner.error('Error!') // [!code ++] ``` ## `picospinner` [`picospinner`](https://github.com/tinylibs/picospinner) has zero dependencies with support for custom symbols, frames, and colors through Node.js built-in styling. ```ts import ora from 'ora' // [!code --] import { Spinner } from 'picospinner' // [!code ++] const spinner = ora('Loading...').start() // [!code --] const spinner = new Spinner('Loading...') // [!code ++] spinner.start() // [!code ++] ``` If you want to customize the color of the spinner, you can specify this when creating an instance: ```ts const spinner = new Spinner('Loading...', { colors: { spinner: 'yellow' } }) ``` --- --- url: /docs/replacements/pako.md description: Modern alternatives to the pako package for zlib, deflate and gzip compression --- # Replacements for `pako` `pako` is a JavaScript port of zlib. Its install size is about 2.5 MB, and most of what it does is now available as a smaller package or built into the runtime. ## `fflate` [`fflate`](https://github.com/101arrowz/fflate) is a much smaller and faster pure JavaScript implementation with synchronous functions close to `pako`'s. Be careful with the names: `pako.deflate` produces the zlib format, which is `zlibSync` in `fflate`. `fflate`'s `deflateSync` is raw deflate, the same as `pako.deflateRaw`. | `pako` | `fflate` | | --------------------------- | ----------------------------- | | `deflate` / `inflate` | `zlibSync` / `unzlibSync` | | `deflateRaw` / `inflateRaw` | `deflateSync` / `inflateSync` | | `gzip` / `ungzip` | `gzipSync` / `gunzipSync` | `pako.inflate` also accepts gzip input. If you relied on that, use `decompressSync`, which detects zlib, gzip and raw deflate. ```ts import * as pako from 'pako' // [!code --] import { zlibSync, unzlibSync } from 'fflate' // [!code ++] const compressed = pako.deflate(data) // [!code --] const compressed = zlibSync(data) // [!code ++] const restored = pako.inflate(compressed) // [!code --] const restored = unzlibSync(compressed) // [!code ++] ``` `pako` 2's `{ to: 'string' }` option has no counterpart (it was also removed in `pako` 3). Decode the bytes with `strFromU8` from `fflate` or with `TextDecoder`. ## `node:zlib` (native, Node.js built-in) Node ships zlib as [`node:zlib`](https://nodejs.org/api/zlib.html), with the same formats under the same names: `deflateSync` / `inflateSync` for zlib, `deflateRawSync` / `inflateRawSync` for raw deflate and `gzipSync` / `gunzipSync` for gzip. Asynchronous and streaming versions are also available. ```ts import * as pako from 'pako' // [!code --] import { deflateSync, inflateSync } from 'node:zlib' // [!code ++] const compressed = pako.deflate(data) // [!code --] const compressed = deflateSync(data) // [!code ++] const restored = pako.inflate(compressed) // [!code --] const restored = inflateSync(compressed) // [!code ++] ``` ## Compression Streams API (native) [`CompressionStream` and `DecompressionStream`](https://developer.mozilla.org/docs/Web/API/Compression_Streams_API) are available in all modern browsers and as globals in Node 18 and later. The `'deflate'`, `'deflate-raw'` and `'gzip'` formats match `pako.deflate`, `pako.deflateRaw` and `pako.gzip`. The API is stream-based and asynchronous, so it fits best where you already work with streams or `await`. ```ts import * as pako from 'pako' // [!code --] const compressed = pako.gzip(data) // [!code --] const compressed = new Uint8Array( // [!code ++] await new Response( // [!code ++] new Blob([data]).stream().pipeThrough(new CompressionStream('gzip')) // [!code ++] ).arrayBuffer() // [!code ++] ) // [!code ++] ``` --- --- url: /docs/replacements/path-exists.md description: Modern alternatives to the path-exists package for checking if a path exists --- # Replacements for `path-exists` ## Async `fs.access` (native, Node.js) Use [`fs/promises.access`](https://nodejs.org/docs/latest/api/fs.html#fspromisesaccesspath-mode) and return a boolean. ```ts import pathExists from 'path-exists' // [!code --] import { access } from 'node:fs/promises' // [!code ++] const exists = await pathExists('/etc/passwd') // [!code --] const exists = await access('/etc/passwd').then( // [!code ++] () => true, // [!code ++] () => false // [!code ++] ) // [!code ++] ``` ## Sync `fs.existsSync` (native, Node.js) Added in v0.1.21: synchronous path/file existence check via [`fs.existsSync`](https://nodejs.org/docs/latest/api/fs.html#fsexistssyncpath). ```ts import pathExists from 'path-exists' // [!code --] import { existsSync } from 'node:fs' // [!code ++] const exists = await pathExists('/etc/passwd') // [!code --] const exists = existsSync('/etc/passwd') // [!code ++] ``` ## Bun [`Bun.file()`](https://bun.sh/reference/bun/BunFile) returns a BunFile with an `.exists()` method. ```ts import pathExists from 'path-exists' // [!code --] const path = '/path/to/package.json' const exists = await pathExists(path) // [!code --] const file = Bun.file(path) // [!code ++] const exists = await file.exists() // boolean [!code ++] ``` --- --- url: /docs/replacements/path-root.md description: >- Native alternatives to the path-root and path-root-regex packages for reading the root of a path --- # Replacements for `path-root` / `path-root-regex` `path-root` returns the root of a path, `/` for an absolute path and an empty string for a relative one. `path-root-regex` is the regular expression behind it, and is only ever pulled in as a dependency of `path-root`. ## In Node.js [`parse`](https://nodejs.org/api/path.html#pathparsepath) already exposes this as its `root` property. ```ts import pathRoot from 'path-root' // [!code --] import { parse } from 'node:path' // [!code ++] pathRoot(filepath) // [!code --] parse(filepath).root // [!code ++] ``` `parse` follows the platform it runs on, so on Linux and macOS it reads `\` and `C:` as ordinary filename characters where `path-root` treated them as roots, and it reads a leading `//` as a plain root rather than a UNC server and share. Reach for `win32.parse` if you actually need to parse Windows paths on a POSIX host. ## In the browser `node:path` is not available, so keep the regular expression. This is the one `path-root-regex` returns. ```ts import pathRootRegex from 'path-root-regex' // [!code --] const ROOT_RE = /^([a-zA-Z]:|[\\/]{2}[^\\/]+[\\/]+[^\\/]+)?([\\/])?/ // [!code ++] const pathRoot = (filepath) => { // [!code ++] if (typeof filepath !== 'string') throw new TypeError('expected a string') // [!code ++] return ROOT_RE.exec(filepath)[0] // [!code ++] } // [!code ++] ``` Keep the type check. Without it the regex coerces its argument, so a non-string returns an empty string instead of throwing the way `path-root` does. --- --- url: /docs/replacements/pbkdf2.md description: >- Native alternatives to the pbkdf2 package for secure, iterative password-based key derivation --- # Replacements for `pbkdf2` ## `crypto.subtle.deriveBits` (native) From [`MDN documentation`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveBits#pbkdf2): ```ts async function deriveKey(password: string, salt: Uint8Array) { const enc = new TextEncoder() const keyMaterial = await crypto.subtle.importKey( 'raw', enc.encode(password), 'PBKDF2', false, ['deriveBits'] ) const derivedBits = await crypto.subtle.deriveBits( { name: 'PBKDF2', salt, iterations: 100000, hash: 'SHA-512' }, keyMaterial, 256 ) return new Uint8Array(derivedBits) } const salt = crypto.getRandomValues(new Uint8Array(16)) const derivedKey = await deriveKey('password', salt) ``` ## `crypto.pbkdf2` (native, since Node.js v0.5.5) ```ts import pbkdf2 from 'pbkdf2' // [!code --] import * as crypto from 'node:crypto' // [!code ++] const salt = crypto.getRandomValues(new Uint8Array(16)) const iterations = 100000 const keylen = 32 const derivedKey = pbkdf2.pbkdf2Sync('password', salt, iterations, keylen, 'sha512') // [!code --] const derivedKey = crypto.pbkdf2Sync('password', salt, iterations, keylen, 'sha512') // [!code ++] ``` --- --- url: /docs/replacements/pkg-dir.md description: >- Modern alternatives to the pkg-dir package for finding package root directories --- # Replacements for `pkg-dir` ## `empathic` [`empathic`](https://github.com/lukeed/empathic) provides a more generic way to find files and directories upwards. The main difference is that `empathic` is *synchronous*, so you should no longer `await` the result. Example: ```ts import { dirname } from 'node:path' // [!code ++] import * as pkg from 'empathic/package' // [!code ++] import { packageDirectory } from 'pkg-dir' // [!code --] const dir = await packageDirectory() // [!code --] const dir = dirname(pkg.up()) // [!code ++] ``` --- --- url: /docs/replacements/portal-vue.md description: >- Modern alternatives to the `portal-vue` package for making portals in Vue applications --- # Replacements for `portal-vue` ## Vue `Teleport` API Since Vue 3, the [Teleport](https://vuejs.org/guide/built-ins/teleport.html) component has been introduced which replaces portal-vue for most use cases, especially modals and overlays. `` only moves DOM nodes to an existing target — it does not manage destinations, layouts, or component structure. ```html

Vue Teleport Example

``` ```html

The content inside of Teleport will render in html body

``` --- --- url: /docs/replacements/portfinder.md description: Modern alternatives to the portfinder package for finding an available port --- # Replacements for `portfinder` ## `get-port` [`get-port`](https://github.com/sindresorhus/get-port) is a lighter and more modern package for finding an available port. Example: ```ts import portfinder from 'portfinder' // [!code --] import getPort from 'get-port' // [!code ++] const port = await portfinder.getPortPromise() // [!code --] const port = await getPort() // [!code ++] ``` --- --- url: /docs/replacements/qs.md description: >- Modern alternatives to the qs package for parsing and serializing query strings --- # Replacements for `qs` ## `URLSearchParams` (native) [`URLSearchParams`](https://developer.mozilla.org/docs/Web/API/URLSearchParams) is built into browsers and Node.js (>= 10). Use it when you don’t need nested objects or automatic array parsing. It preserves multiple values via `getAll`, and `toString()` gives you a URL-safe query string. Example: ```ts import qs from 'qs' // [!code --] const query = 'a=1&a=2&b=3' const obj = qs.parse(query) // [!code --] const sp = new URLSearchParams(query) // [!code ++] const obj = Object.fromEntries(sp) // [!code ++] const a = sp.getAll('a') // [!code ++] ``` ## `fast-querystring` [`fast-querystring`](https://github.com/anonrig/fast-querystring) is tiny and very fast. It handles flat key/value pairs and repeated keys as arrays; it does not support nested objects. Use it when you need arrays but not nesting. Example: ```ts import qs from 'qs' // [!code --] import fqs from 'fast-querystring' // [!code ++] const obj = qs.parse('tag=a&tag=b') // [!code --] const obj = fqs.parse('tag=a&tag=b') // [!code ++] const str = qs.stringify({ tag: ['a', 'b'], q: 'x y' }) // [!code --] const str = fqs.stringify({ tag: ['a', 'b'], q: 'x y' }) // [!code ++] ``` ## `picoquery` [`picoquery`](https://github.com/43081j/picoquery) supports nesting and arrays with a fast single‑pass parser and configurable syntax. v2.x and above are ESM‑only; v1.x is CommonJS and will be maintained with non‑breaking changes. `nestingSyntax: 'js'` offers the highest compatibility with `qs`, though you can pick other syntaxes for performance. Example: ```ts import qs from 'qs' // [!code --] import { parse, stringify } from 'picoquery' // [!code ++] const opts = { // [!code ++] nestingSyntax: 'js', // [!code ++] arrayRepeat: true, // [!code ++] arrayRepeatSyntax: 'bracket' // [!code ++] } // [!code ++] const obj = qs.parse('user[name]=foo&tags[]=bar&tags[]=baz') // [!code --] const obj = parse('user[name]=foo&tags[]=bar&tags[]=baz', opts) // [!code ++] const str = qs.stringify( // [!code --] { user: { name: 'foo' }, tags: ['bar', 'baz'] }, // [!code --] { arrayFormat: 'brackets' } // [!code --] ) // [!code --] const str = stringify({ user: { name: 'foo' }, tags: ['bar', 'baz'] }, opts) // [!code ++] ``` ## `neoqs` [`neoqs`](https://github.com/PuruVJ/neoqs) is a fork of `qs` without legacy polyfills, with TypeScript types included, and with both ESM and CommonJS builds (plus a legacy ES5 mode). Choose it when you want `qs`‑level compatibility with modern packaging options. Example: ```ts import qs from 'qs' // [!code --] import * as qs from 'neoqs' // [!code ++] const obj = qs.parse('a[b][c]=1&arr[]=2&arr[]=3') const str = qs.stringify(obj, { arrayFormat: 'brackets' }) ``` --- --- url: /docs/replacements/react-helmet.md description: Modern alternatives to the react-helmet package --- # Replacements for `react-helmet` ## Support for Document Metadata (native, since React 19) [Support for Document Metadata](https://react.dev/blog/2024/12/05/react-19#support-for-metadata-tags) is available since React 19 Example: ```jsx function BlogPost({ post }) { return (

{post.title}

{post.title}

Eee equals em-see-squared...

) } ``` ## `react-helmet-async` [`react-helmet-async`](https://github.com/staylor/react-helmet-async) is a fork of `react-helmet`. Example: ```jsx import { Helmet } from 'react-helmet' // [!code --] import { Helmet, HelmetProvider } from 'react-helmet-async' // [!code ++] const app = ( // [!code ++] Hello World

Hello World

// [!code ++] ) ``` --- --- url: /docs/replacements/read-package-up.md description: >- Modern alternatives to the read-package-up package for reading package.json files up the directory tree --- # Replacements for `read-package-up` ## `pkg-types` [`pkg-types`](https://github.com/unjs/pkg-types) provides a similar API and strong types. For example: ```ts import { readPackageJSON } from 'pkg-types' // [!code ++] import { readPackageUp } from 'read-package-up' // [!code --] const packageJson = await readPackageUp() // [!code --] const packageJson = await readPackageJSON() // [!code ++] ``` Similarly, you can get hold of the path via `resolvePackageJSON`: ```ts import { resolvePackageJSON } from 'pkg-types' const packageJsonPath = await resolvePackageJSON() ``` ## `empathic` [`empathic`](https://github.com/lukeed/empathic) provides a more generic way to find files and directories upwards. It can be combined with `node:fs` to read `package.json` files: ```ts import fs from 'node:fs/promises' // [!code ++] import * as pkg from 'empathic' // [!code ++] import { readPackageUp } from 'read-package-up' // [!code --] const packageJson = await readPackageUp() // [!code --] const packageJsonPath = pkg.up() // [!code ++] const packageJson = packageJsonPath // [!code ++] ? JSON.parse(await readFile(packageJsonPath, 'utf8')) // [!code ++] : undefined // [!code ++] ``` > \[!NOTE] > This is of course a more manual way to read the `package.json` file, so one of the other options may be more attractive. --- --- url: /docs/replacements/read-pkg-up.md description: >- Modern alternatives to the read-pkg-up package for reading package.json files up the directory tree --- # Replacements for `read-pkg-up` ## `pkg-types` [`pkg-types`](https://github.com/unjs/pkg-types) provides a similar API and strong types. For example: ```ts import { readPackageUp } from 'read-pkg-up' // [!code --] import { readPackageJSON } from 'pkg-types' // [!code ++] const packageJson = await readPackageJSON() // [!code ++] const packageJson = await readPackageUp() // [!code --] ``` Similarly, you can get hold of the path via `resolvePackageJSON`: ```ts import { resolvePackageJSON } from 'pkg-types' const packageJsonPath = await resolvePackageJSON() ``` ## `empathic` [`empathic`](https://github.com/lukeed/empathic) provides a more generic way to find files and directories upwards. It can be combined with `node:fs` to read `package.json` files: ```ts import { readPackageUp } from 'read-pkg-up' // [!code --] import fs from 'node:fs/promises' // [!code ++] import * as pkg from 'empathic' // [!code ++] const packageJson = await readPackageUp() // [!code --] const packageJsonPath = pkg.up() // [!code ++] const packageJson = packageJsonPath // [!code ++] ? JSON.parse(await readFile(packageJsonPath, 'utf8')) // [!code ++] : undefined // [!code ++] ``` > \[!NOTE] > This is of course a more manual way to read the `package.json` file, so one of the other options may be more attractive. --- --- url: /docs/replacements/read-pkg.md description: >- Native Node.js alternatives to the read-pkg package for reading package.json files --- # Replacements for `read-pkg` ## `pkg-types` [`pkg-types`](https://github.com/unjs/pkg-types) provides a similar API and strong types. For example: ```ts import { readPackageJSON } from 'pkg-types' // [!code ++] import { readPackage } from 'read-pkg' // [!code --] const packageJson = await readPackage() // [!code --] const packageJson = await readPackageJSON() // [!code ++] ``` You may also specify a `cwd`: ```ts import { readPackageJSON } from 'pkg-types' const packageJson = await readPackageJson({ cwd }) ``` ## Native `node:fs` You can use `node:fs` to read a known `package.json`: ```ts import fs from 'node:fs/promises' // [!code ++] import { readPackage } from 'read-pkg' // [!code --] const packageJson = await readPackageUp() // [!code --] const packageJson = JSON.parse(await readFile('./package.json', 'utf8')) // [!code ++] ``` > \[!NOTE] > Using this approach, you will have to handle errors yourself (e.g. failure to read the file). --- --- url: /docs/replacements/readable-stream.md description: >- Modern alternatives to the readable-stream package for working with streaming data in Node.js --- # Replacements for `readable-stream` [`readable-stream`](https://github.com/nodejs/readable-stream) mirrors Node’s core streams and works in browsers. In most cases, prefer native options. ## `node:stream` (native, since Node.js v0.9.4) Use the built-in `stream` module ([Node Streams docs](https://nodejs.org/api/stream.html)). ```ts import { Duplex, Readable, Transform, Writable } from 'readable-stream' // [!code --] import { Duplex, Readable, Transform, Writable } from 'node:stream' // [!code ++] ``` ## Web Streams (native, browsers and Node.js 16.5.0+) Use the [Web Streams API (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) in browsers and modern Node. It’s global in Node 18+ ([Node Web Streams docs](https://nodejs.org/api/webstreams.html)); on 16.5–17.x import from `stream/web` ([details](https://nodejs.org/api/webstreams.html#streamweb-the-web-streams-api)). Interop with Node streams is available via [Readable.toWeb](https://nodejs.org/api/stream.html#streamreadabletowebstreamreadable-options) and [Writable.fromWeb](https://nodejs.org/api/stream.html#streamwritablefromwebwritablestream-options). Example: convert a Web ReadableStream (from fetch) to a Node stream and pipe it to a file. ```ts import { Readable } from 'node:stream' import { pipeline } from 'node:stream/promises' import { createWriteStream } from 'node:fs' const res = await fetch('https://example.com/data.txt') // Web ReadableStream const nodeReadable = Readable.fromWeb(res.body) await pipeline(nodeReadable, createWriteStream('data.txt')) ``` --- --- url: /docs/replacements/resolve.md description: Modern alternatives to the resolve package --- # Replacements for `resolve` ## `import.meta.resolve` (native) [`import.meta.resolve`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta/resolve) is built into browsers and Node.js (>= 20). Example: ```ts const path = import.meta.resolve('my-module') ``` ## `exsolve` [`exsolve`](https://github.com/unjs/exsolve) ```ts import { resolveModulePath } from 'exsolve' const path = resolveModulePath('my-module') ``` ## `oxc-resolver` [`oxc-resolver`](https://github.com/oxc-project/oxc-resolver) ```ts import process from 'node:process' import { ResolverFactory } from 'oxc-resolver' const resolver = new ResolverFactory() const { path } = resolver.sync(process.cwd(), 'my-module') ``` --- --- url: /docs/replacements/rimraf.md description: >- Native Node.js alternatives to the rimraf package for recursive directory removal --- # Replacements for `rimraf` ## `fs.rm` (native, Node.js) Node.js v14.14.0 and above provide a native alternative: [`fs.rm`](https://nodejs.org/api/fs.html#fspromisesrmpath-options) and [`fs.rmSync`](https://nodejs.org/api/fs.html#fsrmsyncpath-options). It supports recursive deletion and works as a direct replacement. ### Async methods ```ts import rimraf from 'rimraf' // [!code --] import { rm } from 'node:fs/promises' // [!code ++] await rimraf('./dist') // [!code --] await rm('./dist', { recursive: true, force: true }) // [!code ++] ``` ### Sync methods ```ts import rimraf from 'rimraf' // [!code --] import * as fs from 'node:fs' // [!code ++] rimraf.sync('./dist') // [!code --] fs.rmSync('./dist', { recursive: true, force: true }) // [!code ++] ``` > \[!IMPORTANT] > Remember to set `{ recursive: true, force: true }` to match the behavior of rimraf. ## `fs.rmdir` (native, Node.js before v14.14.0) If you need to support Node.js 12 up to 14.13, you can use [`fs.rmdir`](https://nodejs.org/api/fs.html#fsrmdirpath-options-callback) with the recursive option. This was added in Node v12.10.0, though it’s deprecated as of Node v14. ```ts import rimraf from 'rimraf' // [!code --] import { rmdir } from 'node:fs/promises' // [!code ++] await rimraf('./dist') // [!code --] await rmdir('./dist', { recursive: true }) // [!code ++] ``` ## CLI usage To replace `rimraf` inside npm scripts, you can run Node directly in eval mode: ```sh node -e "require('fs').rmSync('./dist', { recursive: true, force: true, maxRetries: process.platform === 'win32' ? 10 : 0 })" ``` ## `premove` If you are on an older Node.js version (before v12.10) or you specifically need a CLI replacement, you can use [`premove`](https://github.com/lukeed/premove). It provides both an API and a CLI and works on Node.js v8 and newer. ```json { "scripts": { "clean": "rimraf lib", // [!code --] "clean": "premove lib" // [!code ++] } } ``` --- --- url: /docs/replacements/rollup-plugin-babel.md description: Modern alternative to rollup-plugin-babel for using Babel with Rollup --- # Replacements for `rollup-plugin-babel` ## `@rollup/plugin-babel` [`rollup-plugin-babel`](https://github.com/rollup/rollup-plugin-babel) is deprecated. Its replacement, [`@rollup/plugin-babel`](https://github.com/rollup/plugins/tree/master/packages/babel), is maintained in Rollup's official plugins repository. The main migration change is replacing the default `babel` import with a named import. The current plugin also requires an explicit `babelHelpers` option. Example: ```js import babel from 'rollup-plugin-babel' // [!code --] import { babel } from '@rollup/plugin-babel' // [!code ++] export default { plugins: [babel()] // [!code --] plugins: [babel({ babelHelpers: 'bundled' })] // [!code ++] } ``` --- --- url: /docs/replacements/rollup-plugin-commonjs.md description: >- Modern alternatives to the rollup-plugin-commonjs package for bundling CommonJS modules with Rollup --- # Replacements for `rollup-plugin-commonjs` ## `@rollup/plugin-commonjs` [`rollup-plugin-commonjs`](https://github.com/rollup/rollup-plugin-commonjs) is deprecated; its last release (10.1.0) dates from 2019 and npm points to [`@rollup/plugin-commonjs`](https://github.com/rollup/plugins/tree/master/packages/commonjs), the official successor maintained by the Rollup team. The current version needs Rollup 2.68 or newer. The plugin is still a default export, so for most configurations only the import changes. Example: ```js import commonjs from 'rollup-plugin-commonjs' // [!code --] import commonjs from '@rollup/plugin-commonjs' // [!code ++] export default { plugins: [commonjs()] } ``` --- --- url: /docs/replacements/rollup-plugin-node-resolve.md description: >- Modern alternative to rollup-plugin-node-resolve for resolving Node.js modules in Rollup --- # Replacements for `rollup-plugin-node-resolve` ## `@rollup/plugin-node-resolve` [`rollup-plugin-node-resolve`](https://github.com/rollup/rollup-plugin-node-resolve) is deprecated and no longer maintained. [`@rollup/plugin-node-resolve`](https://github.com/rollup/plugins/tree/master/packages/node-resolve) is its official successor, maintained by the Rollup team. The replacement uses a named `nodeResolve` export instead of the previous default export. Example: ```js import resolve from 'rollup-plugin-node-resolve' // [!code --] import { nodeResolve } from '@rollup/plugin-node-resolve' // [!code ++] export default { plugins: [resolve()] // [!code --] plugins: [nodeResolve()] // [!code ++] } ``` --- --- url: /docs/replacements/rollup-plugin-terser.md description: >- Modern alternatives to the rollup-plugin-terser package for minifying Rollup output --- # Replacements for `rollup-plugin-terser` ## `@rollup/plugin-terser` [`rollup-plugin-terser`](https://github.com/TrySound/rollup-plugin-terser) is deprecated and no longer maintained. [`@rollup/plugin-terser`](https://github.com/rollup/plugins/tree/master/packages/terser) is its official successor, maintained by the Rollup team. The options are unchanged, but the plugin is now a default export rather than a named one. Example: ```js import { terser } from 'rollup-plugin-terser' // [!code --] import terser from '@rollup/plugin-terser' // [!code ++] export default { plugins: [terser()] } ``` --- --- url: /docs/replacements/semver.md description: Modern alternatives to the semver package --- # Replacements for `semver` ## `verkit` [`verkit`](https://github.com/sxzz/verkit) is a modern SemVer toolkit designed for ESM and TypeScript projects. It is faster and smaller than `semver`. ```ts import semver from 'semver' // [!code --] import { isLess, satisfies } from 'verkit' // [!code ++] semver.lt(version1, version2) // [!code --] semver.satisfies(version, range) // [!code --] isLess(version1, version2) // [!code ++] satisfies(version, range) // [!code ++] ``` --- --- url: /docs/replacements/shortid.md description: >- Modern, secure alternatives to the shortid package for generating URL‑friendly unique IDs --- # Replacements for `shortid` ## `nanoid` [`nanoid`](https://github.com/ai/nanoid) is a tiny, secure, URL‑friendly, unique string ID generator. It’s also faster than [`shortid`](https://github.com/dylang/shortid). :::info Good to know before migration * `shortid.isValid(id)`: there’s no direct equivalent. Validate with a regex that matches your chosen alphabet and length, e.g. `/^[A-Za-z0-9_-]{21}$/`. * `shortid.seed()`/`shortid.worker()`: not needed and not provided by `nanoid` (it uses a secure random source). Avoid seeded/deterministic IDs for security. ::: ### Basic migration ```ts import shortid from 'shortid' // [!code --] import { nanoid } from 'nanoid' // [!code ++] const id = shortid.generate() // [!code --] const id = nanoid() // [!code ++] => "V1StGXR8_Z5jdHi6B-myT" ``` ### Control length ```ts // shortid produced ~7-14 chars; with nanoid you pick the size explicitly: nanoid(10) // e.g., "NG3oYbq9qE" ``` ### Custom alphabet (replacement for `shortid.characters`) ```ts shortid.characters('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$@') // [!code --] import { customAlphabet } from 'nanoid' // [!code ++] const alphabet = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$@' // [!code ++] const makeId = customAlphabet(alphabet, 12) // [!code ++] const id = makeId() // [!code ++] ``` --- --- url: /docs/replacements/slice-ansi.md description: Modern alternatives for the slice-ansi package for slicing terminal strings --- # Replacements for `slice-ansi` ## `fast-slice-ansi` [`fast-slice-ansi`](https://github.com/43081j/fast-slice-ansi) is a faster and smaller alternative to `slice-ansi` for slicing strings with ANSI escape codes. ```ts import sliceAnsi from 'slice-ansi' // [!code --] import { sliceAnsi } from 'fast-slice-ansi' // [!code ++] import { styleText } from 'node:util' const value = `The quick ${styleText('red', 'brown fox')} jumped over the lazy dog` console.log(sliceAnsi(value, 10, 25)) ``` --- --- url: /docs/replacements/sort-object.md description: Modern alternatives to the sort-object package for sorting object keys --- # Replacements for `sort-object` ## `Object.entries` and `Array.sort` (native) For simple cases: ```ts import sortObj from 'sort-object' // [!code --] const sorted = sortObj(object) // [!code --] // Ascending A→Z const sorted = Object.fromEntries( // [!code ++] Object.entries(object).sort((a, b) => a[0].localeCompare(b[0])) // [!code ++] ) // [!code ++] ``` Replicating `sortBy` (function returns an ordered key list): ```ts import sortObj from 'sort-object' // [!code --] const sorted = sortObj(object, { // [!code --] sortBy: (obj) => { // [!code --] const arr = [] // [!code --] Object.keys(obj).forEach((k) => { // [!code --] if (obj[k].startsWith('a')) // [!code --] arr.push(k) // [!code --] }) // [!code --] return arr.reverse() // [!code --] } // [!code --] }) // [!code --] const sortBy = (obj) => // [!code ++] Object.keys(obj) // [!code ++] .filter((k) => obj[k].startsWith('a')) // [!code ++] .reverse() // [!code ++] const sorted = Object.fromEntries( // [!code ++] sortBy(object).map((k) => [k, object[k]]) // [!code ++] ) // [!code ++] ``` ## `sort-object-keys` [`sort-object-keys`](https://github.com/keithamus/sort-object-keys) is zero‑dependency and matches common `sort-object` use cases (custom order array or comparator). ```ts import sortObj from 'sort-object' // [!code --] import sortObjectKeys from 'sort-object-keys' // [!code ++] // Default A→Z const sorted = sortObj(object) // [!code --] const sorted = sortObjectKeys(object) // [!code ++] // With comparator const sortedByCmp = sortObj(object, { sort: (a, b) => a.localeCompare(b) }) // [!code --] const sortedByCmp = sortObjectKeys(object, (a, b) => a.localeCompare(b)) // [!code ++] ``` ## `sortobject` [`sortobject`](https://github.com/bevry/sortobject) is zero‑dependency and deeply sorts nested objects. ```ts import sortObj from 'sort-object' // [!code --] import sortobject from 'sortobject' // [!code ++] const sorted = sortObj(object) // [!code --] const sorted = sortobject(object) // [!code ++] ``` --- --- url: /docs/replacements/source-map-explorer.md description: >- Modern alternatives to the source-map-explorer package for exploring bundle sourcemaps --- # Replacements for `source-map-explorer` ## `rollup-plugin-visualizer` [`rollup-plugin-visualizer`](https://github.com/btd/rollup-plugin-visualizer) allows you to visualize rollup, rolldown, and vite bundles ### Rollup (`rollup.config.js`) ```js import { visualizer } from 'rollup-plugin-visualizer' export default { plugins: [ // Keep it last. visualizer() ] } ``` ### Rolldown (`rolldown.config.ts`) ```js import { defineConfig, type RolldownPlugin } from 'rolldown' import { visualizer } from 'rollup-plugin-visualizer' export default defineConfig({ plugins: [visualizer() as RolldownPlugin] }) ``` ### Vite (`vite.config.js`) ```js import { visualizer } from 'rollup-plugin-visualizer' export default { plugins: [visualizer()] } ``` ### Vite + TypeScript (`vite.config.ts`) ```ts import { defineConfig, type PluginOption } from 'vite' import { visualizer } from 'rollup-plugin-visualizer' export default defineConfig({ plugins: [visualizer() as PluginOption] }) ``` ## `sonda` [`sonda`](https://github.com/filipsobol/sonda) allows you to visualize vite, rollup, rolldown, esbuild, webpack, and rspack bundles Example: ```js import { defineConfig } from 'vite' import Sonda from 'sonda/vite' export default defineConfig({ build: { sourcemap: true }, plugins: [Sonda()] }) ``` For full docs check their [website](https://sonda.dev/getting-started). ## `webpack-bundle-analyzer` [`webpack-bundle-analyzer`](https://github.com/webpack/webpack-bundle-analyzer) allows you to visualize webpack bundles Example: ```js import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer' module.exports = { plugins: [new BundleAnalyzerPlugin()] } ``` --- --- url: /docs/replacements/split.md description: Native Node.js alternatives to the split package for splitting stream by lines --- # Replacements for `split` ## `readline.createInterface` (native, since Node.js v6.6.0) The [`readline.createInterface`](https://nodejs.org/api/readline.html#readlinecreateinterfaceoptions) method can be used to read a stream line by line, effectively replacing the functionality of the `split` package. Example: ```js import split from 'split' // [!code --] import { createInterface } from 'node:readline' // [!code ++] import * as fs from 'node:fs' const input = fs.createReadStream('file.txt') const stream = input.pipe(split()) // [!code --] stream.on('data', (line) => { // [!code --] fn(line) // [!code --] }) // [!code --] const lines = createInterface({ input, crlfDelay: Infinity }) // [!code ++] for await (const line of lines) { // [!code ++] fn(line) // [!code ++] } // [!code ++] ``` --- --- url: /docs/replacements/sqlite3.md description: Modern alternatives to the sqlite3 package for working with SQLite in Node.js --- # Replacements for `sqlite3` ## `node:sqlite` (native, since Node.js 22.13.0) Node.js ships a built-in SQLite module, [`node:sqlite`](https://nodejs.org/api/sqlite.html), which is the preferred option when you can target a recent Node runtime. Example: ```ts import sqlite3 from 'sqlite3' // [!code --] import { DatabaseSync } from 'node:sqlite' // [!code ++] const db = new sqlite3.Database('app.db') // [!code --] const db = new DatabaseSync('app.db') // [!code ++] db.all('SELECT * FROM users', (err, rows) => {}) // [!code --] const rows = db.prepare('SELECT * FROM users').all() // [!code ++] ``` ## `better-sqlite3` [`better-sqlite3`](https://github.com/WiseLibs/better-sqlite3) is a popular and actively maintained SQLite library for Node.js. Example: ```ts import sqlite3 from 'sqlite3' // [!code --] import Database from 'better-sqlite3' // [!code ++] const db = new sqlite3.Database('app.db') // [!code --] const db = new Database('app.db') // [!code ++] db.all('SELECT * FROM users', (err, rows) => {}) // [!code --] const rows = db.prepare('SELECT * FROM users').all() // [!code ++] ``` ## `@libsql/client` [`@libsql/client`](https://github.com/tursodatabase/libsql-client-ts) is a modern, promise-based client that is fully compatible with SQLite. It supports local SQLite files, remote LibSQL servers, and Turso databases, making it easy to transition to the edge in the future. Example: ```ts import sqlite3 from 'sqlite3' // [!code --] import { createClient } from '@libsql/client' // [!code ++] const db = new sqlite3.Database('app.db') // [!code --] const db = createClient({ url: 'file:app.db' }) // [!code ++] db.all('SELECT * FROM users', (err, rows) => {}) // [!code --] const { rows } = await db.execute('SELECT * FROM users') // [!code ++] ``` --- --- url: /docs/replacements/stream-buffers.md description: >- Modern alternatives to the stream-buffers package for buffering and generating streams in Node.js --- # Replacements for `stream-buffers` ## Utility Consumers (native, Node.js) Since Node.js ≥ 16.7.0 [Utility Consumers](https://nodejs.org/api/webstreams.html#webstreams_utility_consumers) let you consume a Readable stream fully into memory (`Buffer`/`string`/`JSON`/`Blob`/`ArrayBuffer`). Example: ```ts import streamBuffers from 'stream-buffers' // [!code --] import { pipeline } from 'node:stream/promises' // [!code --] import { buffer } from 'node:stream/consumers' // [!code ++] const sink = new streamBuffers.WritableStreamBuffer() // [!code --] await pipeline(readable, sink) // [!code --] const out = sink.getContents() // [!code --] const out = await buffer(readable) // [!code ++] ``` Capturing output when an API expects a Writable example: ```ts import streamBuffers from 'stream-buffers' // [!code --] import { PassThrough } from 'node:stream' // [!code ++] import { buffer, text } from 'node:stream/consumers' // [!code ++] const sink = new streamBuffers.WritableStreamBuffer() // [!code --] const sink = new PassThrough() // [!code ++] const outPromise = buffer(sink) // [!code ++] await someFnThatWritesTo(sink) const out = sink.getContents() // [!code --] sink.end() // [!code ++] const out = await outPromise // [!code ++] ``` Push data over time example: ```ts import streamBuffers from 'stream-buffers' // [!code --] import { Buffer } from 'node:buffer' import { Readable } from 'node:stream' // [!code ++] const rs = new streamBuffers.ReadableStreamBuffer() // [!code --] const rs = new Readable({ read() {} }) // [!code ++] rs.put('first chunk') // [!code --] rs.push('first chunk') // [!code ++] rs.put(Buffer.from('second chunk')) // [!code --] rs.push(Buffer.from('second chunk')) // [!code ++] rs.stop() // [!code --] rs.push(null) // [!code ++] ``` Control chunk size and frequency example: ```ts import { Buffer } from 'node:buffer' import streamBuffers from 'stream-buffers' // [!code --] import { Readable } from 'node:stream' // [!code ++] import { setTimeout } from 'node:timers/promises' // [!code ++] const data = Buffer.from('...your data...') const frequencyMs = 10 const chunkSize = 2048 const rs = new streamBuffers.ReadableStreamBuffer({ frequency: frequencyMs, chunkSize: chunkSize }) // [!code --] rs.put(data) // [!code --] rs.stop() // [!code --] const rs = Readable.from(async function* () { // [!code ++] for (let i = 0; i < data.length; i += chunkSize) { // [!code ++] yield data.slice(i, i + chunkSize) // [!code ++] await setTimeout(frequencyMs) // [!code ++] } // [!code ++] }()) // [!code ++] ``` --- --- url: /docs/replacements/string-width.md description: >- Modern alternatives to the string-width package for measuring the visual width of a string --- # Replacements for `string-width` ## `fast-string-width` [`fast-string-width`](https://github.com/fabiospampinato/fast-string-width) is a drop‑in replacement for `string-width` that’s faster and smaller. ```ts import stringWidth from 'string-width' // [!code --] import stringWidth from 'fast-string-width' // [!code ++] console.log(stringWidth('abc')) // 3 console.log(stringWidth('👩‍👩‍👧‍👦')) // 1 console.log(stringWidth('\u001B[31mhello\u001B[39m')) // 5 ``` ## Bun API (native) If you’re on Bun ≥ 1.0.29, you can use the built‑in [`stringWidth`](https://bun.com/reference/bun/stringWidth): ```ts import stringWidth from 'string-width' // [!code --] import { stringWidth } from 'bun' // [!code ++] console.log(stringWidth('abc')) // 3 console.log(stringWidth('👩‍👩‍👧‍👦')) // 1 console.log(stringWidth('\u001B[31mhello\u001B[39m')) // 5 console.log( stringWidth('\u001B[31mhello\u001B[39m', { countAnsiEscapeCodes: false }) ) // 5 ``` --- --- url: /docs/replacements/strip-ansi.md description: >- Native Node.js alternatives to the strip-ansi package for removing ANSI escape codes from strings --- # Replacements for `strip-ansi` ## `util.stripVTControlCharacters` (native, Node.js) Added in v16.11.0, [`util.stripVTControlCharacters`](https://nodejs.org/api/util.html#utilstripvtcontrolcharactersstr) can be used to strip ANSI escape codes from a string. ```ts import stripAnsi from 'strip-ansi' // [!code --] import { stripVTControlCharacters } from 'node:util' // [!code ++] console.log(stripAnsi('\u001B[4me18e\u001B[0m')) // [!code --] console.log(stripVTControlCharacters('\u001B[4me18e\u001B[0m')) // [!code ++] ``` > \[!NOTE] > Due to [a bug](https://github.com/nodejs/node/issues/53697), in older Node versions this utility doesn't handle ANSI hyperlinks correctly. This behavior has been fixed as of NodeJS v22.10. ## Deno Deno implements the Node `util` API, and also provides [`util.stripVTControlCharacters`](https://docs.deno.com/api/node/util/~/stripVTControlCharacters). The usage is identical: ```ts import stripAnsi from 'strip-ansi' // [!code --] import { stripVTControlCharacters } from 'node:util' // [!code ++] console.log(stripAnsi('\u001B[4me18e\u001B[0m')) // [!code --] console.log(stripVTControlCharacters('\u001B[4me18e\u001B[0m')) // [!code ++] ``` ## Bun ### Using Node‑compatible API Bun also implements Node’s [`util.stripVTControlCharacters`](https://bun.sh/reference/node/util/stripVTControlCharacters) through its Node compat layer: ```ts import stripAnsi from 'strip-ansi' // [!code --] import { stripVTControlCharacters } from 'node:util' // [!code ++] console.log(stripAnsi('\u001B[1mHello\u001B[0m')) // [!code --] console.log(stripVTControlCharacters('\u001B[1mHello\u001B[0m')) // [!code ++] ``` ### Using Bun's native API (>=1.2.21) Since Bun v1.2.21, you can use the built-in [`Bun.stripANSI`](https://bun.com/blog/release-notes/bun-v1.2.21#bun-stripansi-simd-accelerated-ansi-escape-removal) method. ```ts import stripAnsi from 'strip-ansi' // [!code --] import { stripANSI } from 'bun' // [!code ++] console.log(stripAnsi('\u001B[31mHello World\u001B[0m')) // [!code --] console.log(Bun.stripANSI('\u001B[31mHello World\u001B[0m')) // [!code ++] ``` --- --- url: /docs/replacements/tar-fs.md description: >- Lighter alternative to the tar-fs package for packing and extracting tar archives on the filesystem --- # Replacements for `tar-fs` ## `modern-tar` [`tar-fs`](https://github.com/mafintosh/tar-fs) provides filesystem bindings on top of [`tar-stream`](https://github.com/mafintosh/tar-stream). [`modern-tar`](https://github.com/ayuhito/modern-tar) covers the same workflows through [`modern-tar/fs`](https://github.com/ayuhito/modern-tar), with zero dependencies and built-in TypeScript types. Example: ```js import tar from 'tar-fs' // [!code --] import { createReadStream, createWriteStream } from 'node:fs' // [!code ++] import { packTar, unpackTar } from 'modern-tar/fs' // [!code ++] import { pipeline } from 'node:stream/promises' // [!code ++] tar.pack('./my-directory').pipe(createWriteStream('my-tarball.tar')) // [!code --] await pipeline(packTar('./my-directory'), createWriteStream('my-tarball.tar')) // [!code ++] createReadStream('my-other-tarball.tar').pipe( // [!code --] tar.extract('./my-other-directory') // [!code --] ) // [!code --] await pipeline( // [!code ++] createReadStream('my-other-tarball.tar'), // [!code ++] unpackTar('./my-other-directory') // [!code ++] ) // [!code ++] ``` --- --- url: /docs/replacements/tempy.md description: >- Modern alternatives to the temp and tempy packages for creating temporary files and directories --- # Replacements for `temp` / `tempy` ## `fs.mkdtemp` (native, since Node.js v14.x) Node.js has the [`fs.mkdtemp`](https://nodejs.org/api/fs.html#fsmkdtempprefix-options-callback) function for creating a unique temporary directory. Directory cleanup can be done by passing `{recursive: true}` to [`fs.rm`](https://nodejs.org/api/fs.html#fsrmpath-options-callback). Example: ```ts import temp from 'temp' // [!code --] import { mkdtemp, realpath } from 'node:fs/promises' // [!code ++] import { join } from 'node:path' // [!code ++] import { tmpdir } from 'node:os' // [!code ++] const tempDirPath = temp.mkdirSync('foo') // [!code --] const tempDirPath = await mkdtemp(join(await realpath(tmpdir()), 'foo-')) // [!code ++] ``` ## `fs.mkdtempDisposable` (native, since Node.js v24.4.0) Node.js now provides [`fs.mkdtempDisposable`](https://nodejs.org/api/fs.html#fspromisesmkdtempdisposableprefix-options) which leverages the `using` keyword for automatic cleanup. This eliminates the need for `temp.track()` or manual cleanup logic. Example: ```ts import temp from 'temp' // [!code --] import { mkdtempDisposable } from 'node:fs/promises' // [!code ++] import { join } from 'node:path' // [!code ++] import { tmpdir } from 'node:os' // [!code ++] temp.track() // [!code --] const tempDirPath = temp.mkdirSync('foo') // [!code --] await using tempDir = await mkdtempDisposable(join(tmpdir(), 'foo-')) // [!code ++] const tempDirPath = tempDir.path // [!code ++] ``` ## Deno Deno provides built-in [`Deno.makeTempDir`](https://docs.deno.com/api/deno/~/Deno.makeTempDir) and [`Deno.makeTempFile`](https://docs.deno.com/api/deno/~/Deno.makeTempFile) for creating unique temporary directories and files in the system temp directory (or a custom `dir`). You can also set `prefix` and `suffix`. Both return the full path and require `--allow-write`. ```ts import { temporaryDirectory } from 'tempy' // [!code --] const tempDir = temporaryDirectory({ prefix: 'foo-' }) // [!code --] const tempDir = await Deno.makeTempDir({ prefix: 'foo-' }) // [!code ++] ``` ```ts import { temporaryFile } from 'tempy' // [!code --] const tempFile = temporaryFile({ extension: 'txt' }) // [!code --] const tempFile = await Deno.makeTempFile({ suffix: '.txt' }) // [!code ++] ``` > \[!NOTE] > See also: [secure tempfiles in Node.js without dependencies (Advanced Web Machinery)](https://advancedweb.hu/secure-tempfiles-in-nodejs-without-dependencies) --- --- url: /docs/replacements/through.md description: Modern alternatives to the through package --- # Replacements for `through` ## `stream.Writable` (native, Node.js) The native [`stream.Writable`](https://nodejs.org/api/stream.html#class-streamwritable) class can be used to create writable streams. It is a suitable replacement when `through` is being used only as a sink that consumes data, rather than as a duplex/transform stream that forwards data. ```ts import through from 'through' // [!code --] import { Writable } from 'node:stream' // [!code ++] through(fn) // [!code --] new Writable({ // [!code ++] write: (chunk, encoding, callback) => { // [!code ++] fn(chunk) // [!code ++] callback() // [!code ++] } // [!code ++] }) // [!code ++] ``` --- --- url: /docs/replacements/timezone-support.md description: >- Native alternatives to the timezone-support package for listing, resolving and converting time zones --- # Replacements for `timezone-support` `timezone-support` ships its own copy of the IANA time zone database and helpers to convert between zones. Modern runtimes already ship this data: `Intl.supportedValuesOf` lists the available zones, and [`Temporal`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal) covers the conversion helpers. ## `Intl.supportedValuesOf` [`Intl.supportedValuesOf`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/supportedValuesOf) replaces `listTimeZones`: ```ts import { listTimeZones } from 'timezone-support' // [!code --] const timeZones = listTimeZones() // [!code --] const timeZones = Intl.supportedValuesOf('timeZone') // [!code ++] ``` ## `Temporal` `findTimeZone` is no longer needed — pass the IANA identifier directly. Getting the wall-clock time in a zone (`getZonedTime`, `convertDateToTime`): ```ts import { findTimeZone, getZonedTime } from 'timezone-support' // [!code --] const date = new Date() const berlin = findTimeZone('Europe/Berlin') // [!code --] const time = getZonedTime(date, berlin) // [!code --] const time = date.toTemporalInstant().toZonedDateTimeISO('Europe/Berlin') // [!code ++] ``` Getting the UTC offset (`getUTCOffset`): ```ts import { findTimeZone, getUTCOffset } from 'timezone-support' // [!code --] const berlin = findTimeZone('Europe/Berlin') // [!code --] const { offset } = getUTCOffset(new Date(), berlin) // [!code --] const { offset } = Temporal.Now.zonedDateTimeISO('Europe/Berlin') // [!code ++] ``` Note that `Temporal` returns the offset as a string (`'+02:00'`) or via `offsetNanoseconds`, rather than minutes. Getting a timestamp or `Date` from a zoned time (`getUnixTime`, `convertTimeToDate`): ```ts import { findTimeZone, getUnixTime } from 'timezone-support' // [!code --] const berlin = findTimeZone('Europe/Berlin') // [!code --] const time = { year: 2018, month: 9, day: 2, hours: 10, minutes: 0 } // [!code --] const epoch = getUnixTime(time, berlin) // [!code --] const epoch = Temporal.ZonedDateTime.from({ // [!code ++] timeZone: 'Europe/Berlin', // [!code ++] year: 2018, month: 9, day: 2, hour: 10, minute: 0 // [!code ++] }).epochMilliseconds // [!code ++] const date = new Date(epoch) ``` Attaching a zone to a wall-clock time (`setTimeZone`): ```ts import { findTimeZone, setTimeZone } from 'timezone-support' // [!code --] const berlin = findTimeZone('Europe/Berlin') // [!code --] const time = { year: 2018, month: 9, day: 2, hours: 10, minutes: 0 } // [!code --] const berlinTime = setTimeZone(time, berlin) // [!code --] const berlinTime = Temporal.PlainDateTime // [!code ++] .from({ year: 2018, month: 9, day: 2, hour: 10, minute: 0 }) // [!code ++] .toZonedDateTime('Europe/Berlin') // [!code ++] ``` Parsing and formatting (`parseZonedTime`, `formatZonedTime`) — `Temporal` parses ISO 8601 strings and `toLocaleString` (backed by `Intl.DateTimeFormat`) handles output; custom format strings such as `'D.M.YYYY H:mm zZ'` are not supported: ```ts import { parseZonedTime, formatZonedTime } from 'timezone-support/parse-format' // [!code --] const time = parseZonedTime('2.9.2018 10:00 CEST+02:00', 'D.M.YYYY H:mm zZ') // [!code --] const time = Temporal.ZonedDateTime.from('2018-09-02T10:00:00+02:00[Europe/Berlin]') // [!code ++] const output = formatZonedTime(time, 'D.M.YYYY H:mm zZ') // [!code --] const output = time.toLocaleString('de-DE', { dateStyle: 'medium', timeStyle: 'short' }) // [!code ++] ``` --- --- url: /docs/replacements/tokml.md description: Modern replacements for the tokml package for converting GeoJSON to KML --- # Replacements for `tokml` ## `@placemarkio/tokml` [`@placemarkio/tokml`](https://github.com/placemark/tokml) is a maintained alternative for `tokml` package. Example: ```js import tokml from 'tokml' // [!code --] import { toKML } from '@placemarkio/tokml' // [!code ++] const geojson = { type: 'FeatureCollection', features: [ { type: 'Feature', geometry: { type: 'Point', coordinates: [1, 2] }, properties: { name: 'Marker' } } ] } const kml = tokml(geojson) // [!code --] const kml = toKML(geojson) // [!code ++] ``` --- --- url: /docs/replacements/traverse.md description: >- Modern alternative to the traverse package to traverse and transform objects by visiting every node on a recursive walk --- # Replacements for `traverse` ## `neotraverse` [`neotraverse`](https://github.com/puruvj/neotraverse) is a TypeScript rewrite of [`traverse`](https://github.com/ljharb/js-traverse) with no dependencies. It offers a drop‑in compatible build as well as a modern API. ```ts import traverse from 'traverse' // [!code --] import traverse from 'neotraverse' // [!code ++] const obj = [5, 6, -3, [7, 8, -2, 1], { f: 10, g: -13 }] traverse(obj).forEach(function (x) { if (x < 0) this.update(x + 128) }) console.log(obj) ``` --- --- url: /docs/replacements/tsc.md description: Modern alternatives to the tsc package for the TypeScript compiler --- # Replacements for `tsc` The npm package [`tsc`](https://github.com/basarat/tsc) is **not** the TypeScript compiler — it is a deprecated stub that prints *"This is not the tsc command you are looking for"* when run. ## `typescript` The [`typescript`](https://github.com/microsoft/TypeScript) package is the official compiler and provides the real `tsc` binary via `node_modules/.bin`. Replace `tsc` in your dependencies: ```json { "devDependencies": { "tsc": "^2.0.4", // [!code --] "typescript": "^5.9.0" // [!code ++] } } ``` Existing `"build": "tsc"` scripts work unchanged once `typescript` is installed. For `npx` or global installs, use `typescript` — not `tsc`: ```bash npx tsc --init # [!code --] npx --package typescript tsc --init # [!code ++] npm install -g tsc # [!code --] npm install -g typescript # [!code ++] ``` --- --- url: /docs/replacements/tsup.md description: Modern alternative to tsup for bundling TypeScript libraries --- # Replacements for `tsup` ## `tsdown` [tsdown](https://github.com/rolldown/tsdown) is the successor recommended by [tsup's own README](https://github.com/egoist/tsup). The project is effectively in maintenance mode. tsdown is built on Rolldown and is typically faster for library builds, with a largely drop-in config surface. ### Config migration Most `tsup.config.ts` files map 1:1 to `tsdown.config.ts`: ```ts import { defineConfig } from 'tsup' // [!code --] import { defineConfig } from 'tsdown' // [!code ++] export default defineConfig({ entry: ['src/index.ts'], format: ['esm', 'cjs'], dts: true, clean: true }) ``` ### Package scripts ```json { "scripts": { "build": "tsup" // [!code --] "build": "tsdown" // [!code ++] } } ``` ### Automated migration * `npx tsdown-migrate` * `npx skills add rolldown/tsdown --skill tsdown-migrate` --- --- url: /docs/replacements/tweetnacl.md description: >- Native Web Crypto alternatives to tweetnacl for signatures, key exchange, hashing, and random bytes --- # Replacements for `tweetnacl` The [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) natively supports most of the primitives implemented by `tweetnacl`: Ed25519 signatures (`nacl.sign.detached`), X25519 key exchange (`nacl.scalarMult`), SHA-512 hashing (`nacl.hash`), and random bytes (`nacl.randomBytes`). Two differences to keep in mind when migrating: * Web Crypto is asynchronous (`await`), while `tweetnacl` is synchronous. * Ed25519 and X25519 support in Web Crypto is a recent addition across browsers, so older engines may need a fallback. ## Signatures (Ed25519) `nacl.sign.detached` and `nacl.sign.detached.verify` map to `crypto.subtle.sign` and `crypto.subtle.verify` with the `Ed25519` algorithm. Raw public keys can be imported directly with `crypto.subtle.importKey`. Verifying a signature: ```ts import nacl from 'tweetnacl' // [!code --] const isVerified = nacl.sign.detached.verify(message, sig, pubKey) // [!code --] const publicKey = await crypto.subtle.importKey( // [!code ++] 'raw', // [!code ++] pubKey, // [!code ++] { name: 'Ed25519' }, // [!code ++] false, // [!code ++] ['verify'] // [!code ++] ) // [!code ++] const isVerified = await crypto.subtle.verify( // [!code ++] { name: 'Ed25519' }, // [!code ++] publicKey, // [!code ++] sig, // [!code ++] message // [!code ++] ) // [!code ++] ``` Signing a message: ```ts import nacl from 'tweetnacl' // [!code --] const keyPair = nacl.sign.keyPair() // [!code --] const sig = nacl.sign.detached(message, keyPair.secretKey) // [!code --] const keyPair = await crypto.subtle.generateKey( // [!code ++] { name: 'Ed25519' }, // [!code ++] false, // [!code ++] ['sign', 'verify'] // [!code ++] ) // [!code ++] const sig = new Uint8Array( // [!code ++] await crypto.subtle.sign({ name: 'Ed25519' }, keyPair.privateKey, message) // [!code ++] ) // [!code ++] ``` Note that `nacl.sign` (which prepends the signature to the message) has no direct equivalent; use detached signatures and concatenate manually if needed. ## Key exchange (X25519) `nacl.scalarMult` maps to `crypto.subtle.deriveBits` with the `X25519` algorithm: ```ts import nacl from 'tweetnacl' // [!code --] const sharedSecret = nacl.scalarMult(mySecretKey, theirPublicKey) // [!code --] const publicKey = await crypto.subtle.importKey( // [!code ++] 'raw', // [!code ++] theirPublicKey, // [!code ++] { name: 'X25519' }, // [!code ++] false, // [!code ++] [] // [!code ++] ) // [!code ++] const sharedSecret = new Uint8Array( // [!code ++] await crypto.subtle.deriveBits( // [!code ++] { name: 'X25519', public: publicKey }, // [!code ++] myPrivateKey, // [!code ++] 256 // [!code ++] ) // [!code ++] ) // [!code ++] ``` ## Hashing (SHA-512) `nacl.hash` maps to `crypto.subtle.digest`: ```ts import nacl from 'tweetnacl' // [!code --] const hash = nacl.hash(data) // [!code --] const hash = new Uint8Array(await crypto.subtle.digest('SHA-512', data)) // [!code ++] ``` ## Random bytes `nacl.randomBytes` maps to `crypto.getRandomValues`: ```ts import nacl from 'tweetnacl' // [!code --] const bytes = nacl.randomBytes(32) // [!code --] const bytes = crypto.getRandomValues(new Uint8Array(32)) // [!code ++] ``` ## Not covered: `nacl.secretbox` and `nacl.box` XSalsa20-Poly1305 authenticated encryption (`nacl.secretbox`, `nacl.box`) has no Web Crypto equivalent. Code using these primitives cannot migrate to native APIs and should keep using `tweetnacl` or a maintained alternative implementing the same construction. --- --- url: /docs/replacements/uri-js.md description: >- Modern alternatives to uri-js for RFC 3986 URI parsing, resolving, and normalization --- # Replacements for `uri-js` [`uri-js`](https://github.com/garycourt/uri-js) is unmaintained and triggers deprecation warnings on modern Node.js ([due to `punycode`](https://github.com/garycourt/uri-js/pull/95)). ## `URL` (native) Good for standard web URLs (http/https/ws/wss/file/mailto, etc.). * MDN URL: https://developer.mozilla.org/en-US/docs/Web/API/URL * Node.js URL: https://nodejs.org/api/url.html#class-url Example: ```ts import * as URI from 'uri-js' // [!code --] URI.resolve('https://a/b/c/d?q', '../../g') // [!code --] new URL('../../g', 'https://a/b/c/d?q').href // [!code ++] ``` > \[!NOTE] > [WHATWG URL](https://url.spec.whatwg.org/) differs from [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986) in some details and may not cover arbitrary custom schemes/URNs. ## `uri-js-replace` [`uri-js-replace`](https://github.com/andreinwald/uri-js-replace) is a drop-in, zero-dependency replacement for `uri-js` with the same API and no deprecation warnings. ```ts import * as URI from 'uri-js' // [!code --] import * as URI from 'uri-js-replace' // [!code ++] const parsed = URI.parse('uri://user:pass@example.com:123/one/two?q=a#f') const out = URI.serialize({ scheme: 'http', host: 'example.com', fragment: 'footer' }) const norm = URI.normalize('URI://www.example.org/red%09ros\xE9#red') ``` ## `fast-uri` [`fast-uri`](https://github.com/fastify/fast-uri) is a zero-dependency, high-performance RFC 3986 URI toolbox (parse/serialize/resolve/equal) with options similar to `uri-js`. ```ts import * as uri from 'uri-js' // [!code --] import * as uri from 'fast-uri' // [!code ++] uri.parse('uri://user:pass@example.com:123/one/two.three?q1=a1#a') uri.serialize({ scheme: 'http', host: 'example.com', fragment: 'footer' }) uri.resolve('uri://a/b/c/d?q', '../../g') uri.equal('example://a/b/%7Bfoo%7D', 'eXAMPLE://a/./b/../b/%63/%7bfoo%7d') ``` --- --- url: /docs/replacements/utf8.md description: Modern alternatives to the utf8 package for UTF-8 encoding and decoding --- # Replacements for `utf8` Modern Node and browsers provide native UTF-8 APIs, so this dependency is rarely needed. ## TextEncoder/TextDecoder (native) The built-in [`TextEncoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder) and [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) APIs provide a native way to handle UTF-8 encoding and decoding. ```ts const text = '€' const encoder = new TextEncoder() const utf8Bytes = encoder.encode(text) // Uint8Array of UTF-8 bytes // and to decode: const decoder = new TextDecoder('utf-8', { fatal: true }) const decodedText = decoder.decode(utf8Bytes) // "€" ``` ## Buffer (native) Node's built-in [`Buffer`](https://nodejs.org/api/buffer.html) provides both `Buffer.from(str, 'utf8')` and `buf.toString('utf8')` methods for UTF-8 encoding and decoding. ```ts import { Buffer } from 'node:buffer' const text = '€' const utf8Buffer = Buffer.from(text, 'utf8') // Buffer of UTF-8 bytes ``` --- --- url: /docs/replacements/uuidv4.md description: Native alternatives to the uuidv4 package for UUID v4 generation --- # Replacements for `uuidv4` ## `crypto.randomUUID` (native) You can use [`crypto.randomUUID`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID) for UUID v4 generation. Example: ```ts const uuid = crypto.randomUUID() ``` Or in Node.js via [`node:crypto`](https://nodejs.org/api/crypto.html#cryptorandomuuidoptions): ```ts import * as crypto from 'node:crypto' const uuid = crypto.randomUUID() ``` --- --- url: /docs/replacements/vue3-markdown-it.md description: >- Modern alternatives to the vue3-markdown-it package for rendering markdown in Vue --- # Replacements for `vue3-markdown-it` ## `vue-markdown-render` [`vue3-markdown-it`](https://github.com/JanGuillermo/vue3-markdown-it) is archived and unmaintained, and has a known vulnerability through one of its direct dependencies ([GHSA-6v5v-wf23-fmfq](https://github.com/advisories/GHSA-6v5v-wf23-fmfq)). [`vue-markdown-render`](https://github.com/cloudacy/vue-markdown-render) is an actively maintained, near drop-in replacement built on `markdown-it`. In most cases only the import needs to change: ```ts import Markdown from 'vue3-markdown-it' // [!code --] import Markdown from 'vue-markdown-render' // [!code ++] ``` Options and plugins are still passed to the component via props (`options`, `plugins`); check the [vue-markdown-render documentation](https://github.com/cloudacy/vue-markdown-render#readme) if you rely on less common `vue3-markdown-it` props. --- --- url: /docs/replacements/wait-for-expect.md description: Replace wait-for-expect with built-in assertion retries in Vitest or node:test --- # Replacements for `wait-for-expect` Popular test runners such as Vitest and `node:test` provide built-in assertion retries, so projects using them can remove `wait-for-expect`. ## `vitest` [`vi.waitFor()`](https://vitest.dev/api/vi.html#vi-waitfor) is available since Vitest 0.34.5 and accepts synchronous or asynchronous assertion callbacks. It retries callbacks that throw or reject until they succeed or time out. Keep the assertion inside the callback and await the result; returning `false` alone does not trigger a retry. ```ts import waitForExpect from 'wait-for-expect' // [!code --] import { expect, test } from 'vitest' // [!code --] import { expect, test, vi } from 'vitest' // [!code ++] test('service becomes ready', async () => { const checkReady = async () => { expect(await service.isReady()).toBe(true) } await waitForExpect(checkReady, 4500, 50) // [!code --] await vi.waitFor(checkReady, { timeout: 4500, interval: 50 }) // [!code ++] }) ``` If you use `vi.useFakeTimers()`, `vi.waitFor()` automatically advances fake timers by the polling interval on each check. Review tests that previously advanced timers manually. ## `node:test` [`t.waitFor()`](https://nodejs.org/api/test.html#contextwaitforcondition-options) is available on the test context since Node.js 22.14.0 and 23.7.0 and accepts synchronous or asynchronous assertion callbacks. It retries callbacks that throw or reject until they succeed or time out. Keep the assertion inside the callback and await the result; returning `false` alone does not trigger a retry. ```ts import waitForExpect from 'wait-for-expect' // [!code --] import { test } from 'node:test' import assert from 'node:assert/strict' test('service becomes ready', async (t) => { const checkReady = async () => { assert.equal(await service.isReady(), true) } await waitForExpect(checkReady, 4500, 50) // [!code --] await t.waitFor(checkReady, { timeout: 4500, interval: 50 }) // [!code ++] }) ``` --- --- url: /docs/replacements/wellknown.md description: Modern replacements for the wellknown package, a WKT parser and stringifier --- # Replacements for `wellknown` ## `betterknown` [`betterknown`](https://github.com/placemark/betterknown) is a maintained alternative for `wellknown` package. Example: ```js import wellknown from 'wellknown' // [!code --] import { wktToGeoJSON, geoJSONToWkt } from 'betterknown' // [!code ++] wellknown.parse('POINT(1 2)') // [!code --] wktToGeoJSON('POINT(1 2)') // [!code ++] wellknown.stringify({ // [!code --] geoJSONToWkt({ // [!code ++] type: 'Point', coordinates: [1, 2] }) wellknown.stringify({ // [!code --] geoJSONToWkt({ // [!code ++] type: 'Feature', geometry: { type: 'Point', coordinates: [1, 2] }, properties: {} }) ``` --- --- url: /docs/replacements/wrap-ansi.md description: >- Modern replacement for the wrap-ansi package for wrapping terminal strings (including ANSI-colored output) --- # Replacements for `wrap-ansi` ## `fast-wrap-ansi` [`fast-wrap-ansi`](https://github.com/43081j/fast-wrap-ansi) is a drop‑in replacement for `wrap-ansi` that’s faster and smaller. ```ts import wrapAnsi from 'wrap-ansi' // [!code --] import wrapAnsi from 'fast-wrap-ansi' // [!code ++] const value = '\u001B[36mhello-super-long-token-without-spaces\u001B[39m' console.log(wrapAnsi(value, 12, { hard: true, trim: false })) ``` --- --- url: /docs/replacements/xmldom.md description: >- Modern alternatives to the xmldom package for XML DOM parsing and serialization --- # Replacements for `xmldom` ## `@xmldom/xmldom` [`@xmldom/xmldom`](https://github.com/xmldom/xmldom) is the maintained fork of the original `xmldom`. For example: ```ts import { DOMParser, XMLSerializer } from 'xmldom' // [!code --] import { DOMParser, XMLSerializer } from '@xmldom/xmldom' // [!code ++] const doc = new DOMParser().parseFromString(source, 'text/xml') const xml = new XMLSerializer().serializeToString(doc) ``` CommonJS: ```ts const { DOMParser, XMLSerializer } = require('xmldom') // [!code --] const { DOMParser, XMLSerializer } = require('@xmldom/xmldom') // [!code ++] ``` --- --- url: /docs/replacements/yaml-ast-parser.md description: Modern alternatives to yaml-ast-parser for parsing YAML into an AST --- # Replacements for `yaml-ast-parser` ## `yaml` [`yaml`](https://github.com/eemeli/yaml) is an actively maintained YAML parser with support for YAML 1.1 and 1.2, comments, and AST access. `yaml-ast-parser` returns a `YAMLNode` from `load`. With `yaml`, use `parseDocument` and access the root node through `document.contents`. Example: ```ts import { load } from 'yaml-ast-parser' // [!code --] import { parseDocument } from 'yaml' // [!code ++] const ast = load(source) // [!code --] const document = parseDocument(source) // [!code ++] const ast = document.contents // [!code ++] ``` The AST node types differ between the two packages, so code that traverses or modifies nodes will need to use the node APIs provided by `yaml`. --- --- url: /docs/replacements/parseargs.md description: >- Modern alternatives to CLI argument parsing packages using Node.js built-in util.parseArgs --- # Replacements for argument parsers ## `util.parseArgs` (native, since Node.js 16.x) [`util.parseArgs`](https://nodejs.org/api/util.html#utilparseargsconfig) is built into Node.js (since 18.3.0 and 16.17.0) and can replace many common CLI options parsing libraries. Example: ```ts import process from 'node:process' import { parseArgs } from 'node:util' const { values, positionals } = parseArgs({ args: process.argv.slice(2), options: { force: { type: 'boolean', short: 'f' }, output: { type: 'string', short: 'o' } }, allowPositionals: true }) ``` > \[!NOTE] > `parseArgs` only supports `string` and `boolean` types. If you'd like to support stronger types, one of the other options may be a better fit. ## `mri` [`mri`](https://github.com/lukeed/mri) is a minimalistic argument parser that supports both short and long options, as well as positional arguments. Example: ```ts import process from 'node:process' import mri from 'mri' const options = mri(process.argv.slice(2), { alias: { f: 'force', o: 'output' }, boolean: ['force'] }) ``` ## CLI builders If you need a CLI builder, check the the [CLI builders page](https://e18e.dev/docs/replacements/cli-builders). --- --- url: /docs/replacements/cli-builders.md description: Modern alternatives to packages for building CLI applications --- # Replacements for CLI builders ## `sade` [`sade`](https://github.com/lukeed/sade) is a small but powerful tool for building CLI applications for Node.js ```ts import process from 'node:process' import sade from 'sade' const prog = sade('my-cli') prog .version('1.0.5') .option('--global, -g', 'An example global flag') .option('-c, --config', 'Provide path to custom config', 'foo.config.js') prog .command('build ') .describe('Build the source directory. Expects an `index.js` entry file.') .option('-o, --output', 'Change the name of the output file', 'bundle.js') .example('build src build --global --config my-conf.js') .example('build app public -o main.js') .action((src, dest, opts) => { console.log(`> building from ${src} to ${dest}`) console.log('> these are extra opts', opts) }) prog.parse(process.argv) ``` ## `cleye` [`cleye`](https://github.com/privatenumber/cleye) is another powerful tool for building CLI applications for Node.js ```ts import { cli } from 'cleye' // Parse argv const argv = cli({ name: 'greet.js', // Define parameters parameters: [ '', // First name is required '[last name]' // Last name is optional ], // Define flags/options flags: { // Parses `--time` as a string time: { type: String, description: 'Time of day to greet (morning or evening)', default: 'morning' } } }) const name = [argv._.firstName, argv._.lastName].filter(Boolean).join(' ') if (argv.flags.time === 'morning') { console.log(`Good morning ${name}!`) } else { console.log(`Good evening ${name}!`) } ``` ## `cac` [`cac`](https://github.com/cacjs/cac) is a simple yet powerful framework for building CLI applications for Node.js ```ts import cac from 'cac' const cli = cac() cli .command('deploy ', 'Deploy a folder to AWS') .option('--scale [level]', 'Scaling level') .action((folder, options) => { // ... }) cli .command('build [project]', 'Build a project') .option('--out ', 'Output directory') .action((folder, options) => { // ... }) cli.parse() ``` ## Argument parsers If you only need an argument parser, check the [argument parsers page](https://e18e.dev/docs/replacements/parseargs). --- --- url: /docs/replacements/deep-merge.md description: Modern alternatives to packages for deep merging objects --- # Replacements for deep merge packages ## `defu` If you need to deep merge two objects, you can use [`defu`](https://github.com/unjs/defu): ```ts import { defu } from 'defu' const options = defu(object, defaults) ``` ## `@fastify/deepmerge` The [`@fastify/deepmerge`](https://github.com/fastify/deepmerge) package is another fast and small alternative. It also offers more options for customizing the merge behavior than other libraries. ```ts import deepMerge from '@fastify/deepmerge' const options = deepMerge(object, defaults) ``` --- --- url: /sponsor.md description: Information about our sponsorships and how to get involved. --- # Sponsors e18e (Ecosystem Performance) is an initiative to connect the folks and projects working to improve the performance of JS packages. Sponsorships make a lot of our community's work possible. We primarily use them to reward members who have made great steps ahead in the ecosystem, or to fund larger projects which need more attention. You can support us financially, or by getting involved in the community. If you sponsor us on [Open Collective](https://opencollective.com/e18e), your avatar will be shown across all of our projects, as shown below. ![e18e community sponsors](/sponsors.svg) --- --- url: /docs/replacements/schema-validation.md description: Modern alternatives for TypeScript schema validation --- # TypeScript schema validation (shared) This page contains the common, recommended alternatives and examples for TypeScript schema validation libraries. See [Schema Benchmarks](https://schemabenchmarks.dev/download) to explore other schema validation libraries and compare their bundle sizes and runtime behavior. When migrating between schema libraries, verify optionality, empty-string handling, type coercion, unknown object keys, error messages, and synchronous versus asynchronous validation. ## `valibot` [`valibot`](https://valibot.dev/) is a modular schema validation library with a functional, tree-shakable API. Example: ```ts import * as v from 'valibot' const personSchema = v.object({ name: v.pipe(v.string(), v.nonEmpty()), age: v.pipe(v.number(), v.minValue(0)) }) const person = v.parse(personSchema, input) ``` Valibot schemas are required by default; wrap optional fields with `v.optional(...)`. Use `v.nonEmpty()` when empty strings are invalid, and compose further checks with `v.pipe(...)`. `v.parse` is synchronous; use `v.parseAsync` for asynchronous validation. Valibot makes transformations explicit (for example, `v.toNumber()`), and `v.object` removes unknown keys. Use `v.looseObject` when those keys need to be preserved. ## `zod/mini` [`zod/mini`](https://zod.dev/packages/mini) is the tree-shakable, functional API included with Zod 4. Install `zod@^4.0.0` and import the `zod/mini` subpath. Example: ```ts import * as z from 'zod/mini' const personSchema = z.object({ name: z.string().check(z.minLength(1)), age: z.number().check(z.gte(0)) }) const person = personSchema.parse(input) ``` Zod Mini schemas are required by default; wrap optional fields with `z.optional(...)`. Its functional checks are passed to the schema with `.check(...)`, while `parse`, `safeParse`, `parseAsync`, and `safeParseAsync` remain schema methods. As with Valibot, `z.object` strips unknown keys by default; use `z.looseObject` to preserve them. Zod Mini does not load a default error-message locale. Configure one explicitly, for example with `z.config(z.locales.en())`, when callers depend on English error messages.