Skip to content

Maintenance pass: tsup + vitest migration, CI alignment, multi-form bugfix#6

Merged
botre merged 5 commits into
masterfrom
maintenance-pass
May 9, 2026
Merged

Maintenance pass: tsup + vitest migration, CI alignment, multi-form bugfix#6
botre merged 5 commits into
masterfrom
maintenance-pass

Conversation

@botre

@botre botre commented May 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Five commits in dependency order:

  1. Migrate to tsup and vitest. Drops 8 dev deps (webpack, webpack-cli, ts-loader, jest, ts-jest, @types/jest, husky, pretty-quick) in favor of tsup + vitest + jsdom + @vitest/coverage-v8, mirroring @formspark/formson. jest-environment-jsdom was misclassified under dependencies and would have shipped to consumers — fixed. Bumps prettier 2.8 → 3.8 and typescript 4.9 → 5.9. Node engine constraint moves from >=10 (long EOL) to >=20. tsconfig switched to bundler-mode ES2020. npm audit reports 0 vulnerabilities.

  2. Replace continuous-deployment.yml with check.yml + release.yml. Mirrors formson / use-formspark / localview. The previous single workflow only ran on push to master, so PRs were never checked. Action versions brought up to date (actions/checkout@v6, actions/setup-node@v6, phips28/gh-action-bump-version pinned to v11.0.7 instead of @master). npm publish --provenance enabled.

  3. Expanded test coverage and README cleanup. Existing spec only covered the singleton guard. Added integration tests that drive the full URL-params → form-input injection path: standard UTM set, custom data-formtrack-params, no-data-formtrack opt-out, params absent from URL, and update-not-duplicate behavior. README: drop stale CI badge, fix "refers traffic" → "drives traffic".

  4. Scope input lookup to the form, not the whole document — fixes a real bug. A multi-form integration test surfaced that appendOrUpdateInput called document.getElementById, so when two <form data-formtrack> elements were on the same page they fought over the same ID and only the first form ever got an injected input. Switched to formElement.elements.namedItem(name) (form-local lookup by submitted name). The id attribute is still written for any external consumer that queries it. Added 6 more tests pinning: multiple forms, whitespace/empty entries in data-formtrack-params, URLs with no matching params, empty-string param values being skipped, late-mounted forms picked up by the next poll cycle (using vi.useFakeTimers), and unregister() actually clearing the interval.

  5. Preserve the previous UMD surface in the new bundles. The original webpack UMD build used libraryExport: "default" so window.Formtrack and require("@formspark/formtrack") were both the registerPoller function directly. Tsup's IIFE and CJS outputs default to a { default: fn, __esModule: true } namespace — silently breaking any consumer relying on the function surface. Re-exported registerPoller as the default of src/index.ts and added a per-format footer that flattens the namespace back to the function while keeping .default and __esModule for ESM/CJS interop:

    // IIFE
    Formtrack = Object.assign(Formtrack.default, Formtrack);
    // CJS
    module.exports = Object.assign(module.exports.default, module.exports);

    Also enabled minify: true, which (combined with the polyfill compressing well) drops the IIFE bundle from 12.79 KB to 5.23 KB.

Numbers

  • Tests: 16/16 pass (was 5/5 before this PR)
  • Coverage: 100% statements / branches / functions / lines
  • Bundle output: ESM 1.03 KB, CJS 1.55 KB, IIFE 5.23 KB, plus .d.ts and .d.mts
  • npm audit: 0 vulnerabilities
  • Dev deps removed: 8 (webpack ecosystem + jest ecosystem + husky + pretty-quick)

End-to-end verification of the bundles

Built each format and loaded into a fresh JSDOM instance with ?utm_source=google&utm_campaign=spring&utm_medium=cpc, asserting both API shape and behavior. 13/13 assertions pass:

Bundle Surface Auto-registers Function callable
IIFE (<script> from unpkg) window.Formtrack is registerPoller ✅ inputs injected
CJS (require()) returns registerPoller directly, with .default back-reference
ESM (import F from) default export is registerPoller

Test plan

  • npm test — 16/16 pass
  • npx vitest run --coverage — 100% across all metrics
  • npm run build — ESM + CJS + IIFE + DTS build cleanly, minified
  • End-to-end JSDOM smoke test across all three bundles — 13/13
  • npm audit — 0 vulnerabilities
  • Confirm phips28/gh-action-bump-version@v11.0.7 finds the version-bump trigger in your typical commit conventions, otherwise auto-bump won't fire

botre added 5 commits May 9, 2026 09:07
Replace the webpack + jest + ts-jest + husky toolchain with tsup +
vitest, mirroring the @formspark/formson setup. Drops 8 dev
dependencies (jest, ts-jest, @types/jest, jest-environment-jsdom,
ts-loader, webpack, webpack-cli, husky, pretty-quick) in favor of
tsup, vitest, jsdom, and @vitest/coverage-v8.

jest-environment-jsdom was previously listed under "dependencies" by
mistake and would have shipped to consumers; it is now devDeps-only.

Bumps prettier 2.8 -> 3.8 and typescript 4.9 -> 5.9. tsconfig is
updated to bundler-mode ES2020 to match formson. The Node engine
constraint moves from >=10 (long EOL) to >=20.

The package now publishes esm + cjs + iife + dts builds. main, module,
and unpkg fields are wired so unpkg.com/@formspark/formtrack still
resolves to the IIFE bundle for the documented script-tag usage.
Mirror the @formspark/formson and @formspark/use-formspark setup:
check.yml runs build + tests on PRs and via workflow_call (Node 22.x),
release.yml runs on push to master, calls check, then bumps the
version from commit messages and publishes to npm with provenance
(Node 24.x).

The previous single workflow ran on master only, so PRs were not
checked at all. Action versions are also brought up to date
(actions/checkout@v6, actions/setup-node@v6, gh-action-bump-version
pinned to v11.0.7 instead of @master).
Existing spec only verified the singleton guard. Added integration
tests that drive the full path: URL params -> form input injection,
including the standard UTM set, custom data-formtrack-params, the
opt-out (no data-formtrack attribute), parameters absent from the
URL, and the update-not-duplicate behavior on subsequent polls.

Coverage is now 100% statements / branches / functions / lines.

README: drop the stale Continuous-deployment badge and fix
"refers traffic" -> "drives traffic".
A multi-form integration test surfaced a real bug: appendOrUpdateInput
called document.getElementById, so when two <form data-formtrack>
elements were on the same page they fought over the same ID and only
the first form ever got an injected input. Switch to looking up by
name within formElement.elements, which is form-local and matches the
semantic the form actually submits with. The id attribute is still
written for backwards compatibility with anyone querying it.

Adds tests for: multiple forms on a page, whitespace and empty entries
in data-formtrack-params, URLs with no matching params, empty-string
param values being skipped, late-mounted forms picked up by the next
poll cycle (using fake timers), and unregister actually clearing the
interval.
The original webpack UMD build used libraryExport: "default" so
window.Formtrack and require("@formspark/formtrack") were both the
registerPoller function directly. Tsup's IIFE and CJS outputs
default to a namespace shape ({ default: fn, __esModule: true }),
which would silently break any consumer relying on the function
surface even though our README only documents the auto-registering
script-tag use case.

Re-export registerPoller as the default of src/index.ts and append a
tiny footer to each bundle that flattens the namespace back to the
function while keeping the .default property and __esModule flag for
ESM/CJS interop:

  IIFE: Formtrack = Object.assign(Formtrack.default, Formtrack);
  CJS:  module.exports = Object.assign(module.exports.default, module.exports);

Also enable minify, which (combined with the polyfill being smaller
under minification) drops the IIFE bundle from 12.79 KB to 5.23 KB.

Verified end-to-end:
  - require("./dist/index.js") returns the function, .default still works
  - jsdom-loaded IIFE exposes window.Formtrack as the callable function
  - 16/16 vitest tests still pass
@botre botre changed the title Maintenance pass: switch to tsup + vitest, align CI, expand tests Maintenance pass: tsup + vitest migration, CI alignment, multi-form bugfix May 9, 2026
@botre botre merged commit 018dd7a into master May 9, 2026
1 check passed
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