Skip to content

Export a Plugin type for the plugin API - #408

Merged
ai merged 2 commits into
ai:mainfrom
JounQin:feat/plugin-types
Oct 7, 2026
Merged

ai merged 2 commits into
ai:mainfrom
JounQin:feat/plugin-types

Conversation

@JounQin

@JounQin JounQin commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Closes #407.

The plugin contract was untyped: every third-party plugin had to re-declare it (for example size-limit-node-esbuild ships a shim.d.ts), and the first-party plugin packages shipped no types at all.

Core types

size-limit now exports:

  • Plugin — the plugin object: name, before, finally, and every step<N> / wait<N> hook.
  • PluginHook — the (config, check) signature the hooks are called with.
  • PluginStep — the step numbers calc() runs. It calls step0 … step100 for every plugin, so all of them are typed, not only the numbers the plugins in this repository happen to use.
  • PluginConfig — the run configuration the hooks receive.
  • PluginCheck — the check after get-config.js normalized it, together with the fields the plugins which ran before have added (bundles, size, esbuildConfig, webpackConfig, loadTime, …).

The plugins argument of the JS API is typed with them too.

Every package ships its own types

All thirteen packages, size-limit included, declare the declarations in exports — "types" before "default" — and ship an index.d.ts next to index.js, so import esbuildPlugin from '@size-limit/esbuild' is typed without a local shim. The types condition is what resolution reads when a package has an exports map, and size-limit itself was the only package with a top-level types field before; that field is gone, since the declarations sit next to the entry and these packages require Node 22+ anyway.

That is what makes the shim.d.ts in size-limit-node-esbuild (and the equivalents needed by @size-limit/esbuild / @size-limit/file) unnecessary.

One fix

TimeOptions.latency is optional now: get-config.js only normalizes it when it is set, and fixtures/time-network-speed already configures time without it.

Verification

  • oxlint is clean, and the core package's tests pass (vitest run packages/size-limit/test: 148 tests).
  • The types were checked with tsc --strict against a scratch file covering a plugin written by hand, every step/wait name, a step outside 0…100 and a missing name being rejected, extending Plugin, importing every plugin package, and calling the JS API — under both nodenext and bundler resolution.
  • The packed tarballs were checked with arethetypeswrong: node10, node16 (from ESM), node16 (from CJS) and bundler all resolve the declarations. The only warning left is the pre-existing “a require call resolved to an ESM JavaScript file”, which these ESM-only packages cannot avoid without a dual build.
  • vitest run on the whole repository: 268 of 269 tests pass on the machine I used. packages/time/test/get-running-time.test.js fails there because it has no Chrome, and it fails the same way on a clean main, so it is unrelated to this change.

The plugin contract was untyped, so every third-party plugin had to
re-declare it, and the first-party plugins shipped no types at all.

Add `Plugin`, `PluginHook`, `PluginStep`, `PluginConfig` and
`PluginCheck` to `size-limit` and type the `plugins` argument of the JS
API with them. Steps are typed for every number `calc()` runs, `0` to
`100`, not only the ones used by the plugins in this repository.

Ship a declaration file and a `types` field for every plugin package, so
`@size-limit/*` imports are typed without a local shim.

`TimeOptions.latency` is optional: a check can set only `networkSpeed`,
which `fixtures/time-network-speed` already does.

Closes ai#407
Copilot AI balanced review requested due to automatic review settings October 7, 2026 05:31

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

When a package has an `exports` map, the `types` condition is what modern
TypeScript resolution reads, and it is what arethetypeswrong checks.
The declarations sit next to `index.js`, so the ESM-only packages do not
need the top-level field either: the node10 column stays green through
the root `index.d.ts`.

Checked on the packed tarballs with arethetypeswrong: node10, node16
(from ESM), node16 (from CJS) and bundler all resolve the declarations;
the CJS row keeps its pre-existing ESM-only warning, which no types
declaration can remove.
@JounQin JounQin closed this Oct 7, 2026
@JounQin JounQin reopened this Oct 7, 2026
@ai
ai merged commit 46a50fc into ai:main Oct 7, 2026
3 checks passed
@ai

ai commented Oct 7, 2026

Copy link
Copy Markdown
Owner

Thanks. Can you check that everything is ok using github version?

Do you need release of types?

@JounQin

JounQin commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Verified against the GitHub version, in un-ts/size-limit (the workspace holding size-limit-node-esbuild and size-limit-preset-node-lib):

  • cloned this branch from GitHub (737b7e8) and packed size-limit, @size-limit/esbuild and @size-limit/file with the repository's own release path (scripts/resolve-workspaces.js + npm pack), then installed those tarballs over the registry versions;
  • deleted both shim.d.ts files, since these packages no longer have to re-declare @size-limit/esbuild / @size-limit/file;
  • yarn build, yarn lint, yarn test:unit (10 tests, 100% statements), yarn test:e2e (this size-limit with the local plugins: 408 B / 900 B and 351 B / 400 B) and yarn typecov (100%, 501/501) all pass.

What a consumer has to change: the type names (SizeLimitPlugin → Plugin, SizeLimitCheck → PluginCheck, SizeLimitPluginConfig → PluginConfig, all from size-limit), and the hooks are optional on Plugin, so calling one needs ?. or an interface that narrows it — interface NodeEsbuildPlugin extends Plugin { step20: Step } works well.

Two things I ran into while porting, in case you prefer them in this PR rather than separately:

  • Check.modifyEsbuildConfig (and modifyRolldownConfig / modifyWebpackConfig) is a generic function type, so the usual config => ({ ...config, external: [] }) does not compile; the shim was working around it with (config: BuildOptions) => BuildOptions. Opened Let a config type the modify config options #409 for it.
  • A test-built PluginConfig has to pass checks.

On the release: the shim can only be deleted for published consumers once the types are out, so a release would be welcome — but the check above ran against this branch, so nothing here is blocked on it.

@git-credential-broker
git-credential-broker Bot deleted the feat/plugin-types branch October 7, 2026 22:08
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.

Export a Plugin type for the plugin API

3 participants