Skip to content

Fix plugin type declarations for NodeNext module resolution#135

Open
TrevorSayre wants to merge 1 commit into
omgovich:masterfrom
TrevorSayre:master
Open

Fix plugin type declarations for NodeNext module resolution#135
TrevorSayre wants to merge 1 commit into
omgovich:masterfrom
TrevorSayre:master

Conversation

@TrevorSayre
Copy link
Copy Markdown

@TrevorSayre TrevorSayre commented May 28, 2026

Fix plugin type declarations for NodeNext module resolution

Problem

When using moduleResolution: NodeNext (or Node16) in TypeScript, importing a plugin and passing it to extend() produces a type error:

import namesPlugin from "colord/plugins/names";

extend([namesPlugin]);
// Error: Type 'typeof import("node_modules/colord/plugins/names")' is not assignable to type 'Plugin'.
// Type 'typeof import("node_modules/colord/plugins/names")' provides no match for the signature '(ColordClass: typeof Colord, parsers: Parsers): void'.

Root Cause

The generated .d.ts files for plugins use export default function:

import { Colord } from "../colord";
import { Parsers } from "../types";
export default function namesPlugin(ColordClass: typeof Colord, parsers: Parsers): void;

Under moduleResolution: NodeNext, TypeScript resolves import x from "module" to the module namespace type (the typeof import(...) type) rather than the default export's type. This causes the function signature check to fail.

Solution

Added a post-build script (fix-plugin-types.mjs) that transforms the generated .d.ts files from export default function to declare function + export =:

import { Colord } from "../colord";
import { Parsers } from "../types";
declare function namesPlugin(ColordClass: typeof Colord, parsers: Parsers): void;
export = namesPlugin;

With export =, the module itself becomes the callable function type, eliminating the mismatch under NodeNext resolution.

Why this approach?

  1. Source compatibility: The source files remain ESM-compatible with export default function
  2. Type correctness: The export = pattern is the TypeScript-recommended way to type CommonJS modules that export a single value
  3. Automated: The transformation happens automatically during build via npm run build
  4. Minimal overhead: Single post-processing script, no changes to rollup config needed

The plugin source files were also updated to use export default function declarations instead of const arrow functions. This produces cleaner .d.ts files and makes the post-processing transformation straightforward.

Without the post-build script, you'd need to either:

  • Use CommonJS in source (breaks ESM consumers)
  • Manually maintain .d.ts files (error-prone)
  • Override TypeScript's declaration emitter (complex)

Changes Made

  • Modified all 10 plugin source files (src/plugins/*.ts) to use export default function declarations
  • Added fix-plugin-types.mjs post-build script to transform plugin .d.ts files
  • Updated package.json build script to run the fix after rollup

Note on fix-plugin-types.mjs: Uses ES modules (.mjs) for cleaner syntax. Requires Node.js 14+ (Node.js 12, the version before 14, was EOL on April 30, 2022) for native ESM support. This is a build-time script only, not shipped to users.

Verification

  • All 77 tests pass
  • TypeScript type check passes
  • Tested with moduleResolution: NodeNext in a separate project (no type errors)
  • Works with all moduleResolution values: node, bundler, classic, NodeNext, Node16
  • No runtime changes (compiled JS is identical)

So ultimately:

  • Backwards compatible. No breaking changes for existing users.
  • Safe for JavaScript users. Runtime behavior unchanged; only type declarations affected.
  • Great for TypeScript users. Fixes the Node16/NodeNext module resolution type error that previously required @ts-expect-error.

Thank you for your consideration!

Related

Note on PR #118: That PR proposes restructuring the package.json exports map (nesting types under import/require), but #95 already solved the exports issue with the standard TypeScript approach. #118 also does not address the core plugin type declaration issue. This PR fixes the remaining problem: the export default function pattern in .d.ts files still causes the TS2322 type error under Node16/NodeNext module resolution. I fix that by transforming the generated declarations to export =, which is the TypeScript-recommended pattern for callable module exports.

@TrevorSayre TrevorSayre force-pushed the master branch 2 times, most recently from ab69535 to e1f76fe Compare May 28, 2026 22:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant