Skip to content

Comments

Include types in .d.ts files for unresolved or unmatched module imports#302

Merged
mizdra merged 1 commit intomainfrom
generate-type-for-unmatched-modules
Jan 12, 2026
Merged

Include types in .d.ts files for unresolved or unmatched module imports#302
mizdra merged 1 commit intomainfrom
generate-type-for-unmatched-modules

Conversation

@mizdra
Copy link
Owner

@mizdra mizdra commented Jan 12, 2026

Background

Currently, some token importers are not included in the type definitions. For example, consider the following case:

/* src/a.module.css */
@import './unresolved.module.css';
@import './unmatched.css';
.a_1 { color: red; }
/* src/unmatched.css */
body {
  margin: 0;
}

In this situation, the current CSS Modules Kit generates the following type definitions:

// generated/src/a.module.css.d.ts
// @ts-nocheck
declare const styles = {
  a_1: '' as readonly string,
  ...(await import('./unresolved.module.css')).default,
};

In short, specifiers that cannot be resolved (e.g. ./unresolved.module.css) are included in the type definitions. However, specifiers that can be resolved but do not match include/exclude patterns, or modules that are not CSS Modules, are not included.

This behavior has an issue: the generated type definitions may change depending on whether the file exists. If src/unmatched.css does not exist, the generated type definitions become:

// generated/src/a.module.css.d.ts
// @ts-nocheck
declare const styles = {
  a_1: '' as readonly string,
  ...(await import('./unresolved.module.css')).default,
  ...(await import('./unmatched.css')).default,
};

If the generated type definitions change depending on file existence, implementing watch mode becomes more complex, and parallelizing code generation becomes harder.

Proposal

In principle, token importers with specifiers that cannot be resolved are still included in the type definitions. For example, consider the following:

/* src/a.module.css */
@import './unresolved.module.css';
@import './unmatched.css';
.a_1 { color: red; }

In this case, CSS Modules Kit generates the following type definitions:

// generated/src/a.module.css.d.ts
// @ts-nocheck
declare const styles = {
  a_1: '' as readonly string,
  ...(await import('./unresolved.module.css')).default,
  ...(await import('./unmatched.css')).default,
};

Even if ./unresolved.module.css or ./unmatched.css does not exist, the same type definitions are generated. It is important that the generated type definitions do not change depending on whether the files exist. This provides the following benefits:

  • Simplifies the watch mode implementation
    • Only the type definitions for changed files need to be regenerated
  • Makes it easier to parallelize code generation
    • Type definitions can be generated independently per file

However, as an exception, URL specifiers are not included in the type definitions, because URL specifiers are typically resolved at runtime.

Drawbacks

import('./unresolved.module.css') and import('./unmatched.css') are typed as any, and they are spread into the styles object via {...any}. As a result, styles ends up being typed as any, and even tokens that should exist (e.g. styles.a_1) become any.

 2026-01-12 18 05 48

This is a long-standing issue in css-modules-kit. However, with this change—where unmatched token importers are now included in the type definitions—this problem becomes more noticeable.

I plan to fix this issue in a separate Pull Request later (ref: #303).

@changeset-bot
Copy link

changeset-bot bot commented Jan 12, 2026

🦋 Changeset detected

Latest commit: 59532bf

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 6 packages
Name Type
@css-modules-kit/ts-plugin Minor
@css-modules-kit/codegen Minor
@css-modules-kit/core Minor
css-modules-kit-vscode Patch
@css-modules-kit/stylelint-plugin Patch
@css-modules-kit/eslint-plugin Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@mizdra mizdra added the Type: Breaking Change Includes breaking changes label Jan 12, 2026
@mizdra mizdra force-pushed the generate-type-for-unmatched-modules branch from d097c09 to 59532bf Compare January 12, 2026 08:44
@mizdra mizdra marked this pull request as ready for review January 12, 2026 09:10
@mizdra mizdra merged commit 32ecdc2 into main Jan 12, 2026
18 checks passed
@mizdra mizdra deleted the generate-type-for-unmatched-modules branch January 12, 2026 09:11
@github-actions github-actions bot mentioned this pull request Jan 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Type: Breaking Change Includes breaking changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant