Status: planning. Not started. Current released version: 12.2.0 (
master). Target version: v14 (skipping 13). Companion doc:PLAN_V15.md— work explicitly deferred from v14.
This is the working plan for the next major release. It is intentionally detailed so a future session can pick up cold without re-litigating decisions already made.
- Rewrite the entire
src/tree from.mjs+ parallel hand-written.d.tsto a single TypeScript source of truth. - Eliminate
src/types/as a separate hand-maintained tree — types are emitted from.tssources. - Reduce minified bundle size by removing legacy DOM-compat helpers and feature-detect code that is no longer needed at v14's baseline.
- Zero changes to the public runtime API. A v12 user upgrading to v14 should change nothing in their code (only their type errors might tighten).
- Core decomposition. The current
Object.assign(Swiper.prototype, prototypes)pattern insrc/core/core.mjsstays. Tree-shaking the core's own sub-modules (translate,transition,loop,grabCursor, etc.) into opt-in pieces is v15. SeePLAN_V15.md. - Generic
Swiper<TModules>types. Decided against — see §5. - Collapsing the dual touch+pointer event path into pointer-only. Possible win, but behavior-risky on iOS WebView and worth its own dedicated cycle. Note it here, reconsider in v15.
- Effect-module refactors. Already factored out into
swiper-effect-utils; don't churn them. - Framework wrapper API changes. Migrate them to TS, but keep their public API identical.
Policy: last 2 years of evergreen browsers. Concretely (as of 2026-05):
- Chrome / Edge: 110+
- Safari: 16.4+ (iOS 16.4+)
- Firefox: 110+
- No legacy Android WebView, no IE, no pre-16 iOS.
What this unlocks (use freely, no detection needed):
ResizeObserver,MutationObserver,IntersectionObserver- Pointer Events
scroll-behavior: smooth(dropsupport.smoothScroll)element.scrollIntoView({ behavior })classList.add/remove(...multi)Element.closest,Element.matches- Optional chaining, nullish coalescing in source (no transpile needed for runtime targets)
globalThis
What still needs runtime detection:
support.touch— kept, but simplified to a single inline expression (noDocumentTouchlegacy check).device.ios/device.android— kept. iOS-specific quirks (rubber-band scrolling, momentum, 3D transform glitches) still differ. Simplify the detection — drop the iPad-on-MacIntel screen-size table if Safari on iPadOS now reports correctly under modern Safari, otherwise keep but trim.browser.need3dFixfor iOS WebView — keep. iOS WebView still misrenders certain 3D transforms; this is a real bug, not a vintage one.browser.needPerspectiveFix(Safari < 16.2) — delete. Below baseline.
- Replace Babel transpile in
scripts/build.jswithtsc(orrollup-plugin-typescript2/@rollup/plugin-typescript). Verify decision early — Babel handles JSX for the React wrapper today. tsconfig.jsonat repo root with:target: "es2022"(matches baseline)module: "esnext",moduleResolution: "bundler"strict: truedeclaration: true,declarationMap: truenoUncheckedIndexedAccess: true(good hygiene for the array-heavy slide code)
- Keep
.mjsextension on emitted runtime files for ecosystem compat. - The
dist/shape (swiper.mjs,swiper-bundle.mjs,swiper-element.mjs,swiper-react.mjs,swiper-vue.mjsplus their.d.ts) must stay byte-for-byte identical in surface. Tests should snapshot the export list.
Decided. Every module declares its own augmentation of the central Swiper interface and SwiperOptions. Example shape:
// src/modules/navigation/navigation.ts
import type { Swiper, SwiperOptions, SwiperEvents } from '../../core/swiper';
export interface NavigationOptions {
/* ... */
}
export interface NavigationMethods {
/* ... */
}
export interface NavigationEvents {
navigationHide: (swiper: Swiper) => void;
navigationShow: (swiper: Swiper) => void;
// ...
}
declare module '../../core/swiper' {
interface Swiper {
navigation: NavigationMethods;
}
interface SwiperOptions {
navigation?: NavigationOptions;
}
interface SwiperEvents extends NavigationEvents {}
}
export function Navigation(/* ModuleContext */) {
/* runtime */
}User-facing code does not change. As long as a user imports { Navigation } from 'swiper/modules' (which they already must, to register at runtime), the augmentation comes with it. Confirmed trade-off: augmentations are global per-process, not per-instance. Acceptable.
Decision: Swiper is declared as both an interface and a class, in that order, in src/core/swiper.ts:
export interface Swiper {
/* properties + method signatures */
}
export class Swiper {
/* runtime — implements the interface */
}This lets module files target the interface with declare module '../../core/swiper' { interface Swiper { ... } }, which merges cleanly with the class. Without this split, the class declaration would collide with augmentation.
The core's prototypes object (lines ~36–49 of src/core/core.mjs) merges runtime methods onto Swiper.prototype. For v14 we keep the runtime pattern but type it explicitly: each prototype sub-module (update, translate, etc.) exports an object whose method signatures are added to interface Swiper via the same augmentation mechanism. The runtime Object.assign stays. This is intentionally a no-op refactor — decomposition is v15.
Replace ssr-window with inline guards. Pattern:
const isBrowser = typeof document !== 'undefined';
// at the few places that legitimately need window/document, just reference them
// (TS knows the type via lib.dom.d.ts)Audit and delete getWindow() / getDocument() call sites — there are ~dozens, but most are reflexive. The few that matter are in get-support, get-device, get-browser, and event handlers.
Considered and rejected. Trade-off was: precise per-instance types, at the cost of requiring as const on modules: [...] at every call site, and far worse error messages once SwiperOptions (also module-augmented) flows through the generic. Augmentation matches what the JS ecosystem (Vue plugins, Pinia, Three.js loaders, Fastify) does. Re-evaluate if there's user demand, but not in v14.
Each phase should land as its own PR/commit on a v14 branch. Build + size snapshot run after each.
Phase 0 — infra
- Add
tsconfig.json, switch build pipeline, set up CI fortsc --noEmit. - Add a public-API contract test:
tests/dist-contract.test.tsthat imports fromdist/and exercises every public method/property/event name. - Snapshot current minified bundle sizes. Track per-phase deltas.
Phase 1 — shared utils
src/shared/utils.mjs→utils.ts. Inline the helpers being deleted (see §7).get-support.ts,get-device.ts,get-browser.ts— convert + simplify per §3.- Delete
ssr-windowdependency.
Phase 2 — core
src/core/*.mjs→.ts. Declareinterface Swiper+class Swiper. Re-typeparams,el,wrapperEl,slides, the event emitter,touchEventsData, etc.- Validate the augmentation pattern by typing one tiny core sub-module first (e.g.,
grab-cursor). - Keep
Object.assign(Swiper.prototype, prototypes)working — just typed.
Phase 3 — modules (in this order)
- Easy state-only modules:
a11y,keyboard,mousewheel,hash-navigation,history. - DOM-heavy stable modules:
navigation,pagination,scrollbar. - Behavior modules:
autoplay,free-mode,controller,thumbs,parallax,zoom. - Grid/Virtual/Manipulation.
- Effects:
effect-fade,effect-cube,effect-coverflow,effect-flip,effect-creative,effect-cards. Effects sharesrc/swiper-effect-utils.mjs— migrate that first.
Migrate one module at a time. After each, the augmentation should produce correct autocomplete and no TS errors against a tiny demo file.
Phase 4 — framework wrappers
src/react/→ TS + JSX (React types already a devDep).src/vue/→ TS. Trickiest because of Vue's macros around emits/props — may needdefineComponentshape audit.src/element/(Custom Element) → TS. Should be a clean translation.
Phase 5 — cleanup + release
- Delete
src/types/. Sources are now canonical. - Update
package.jsonexportsmap if anything shifted. - Regenerate
CHANGELOG.md. Note in migration guide: "no source code changes required; types may surface latent issues."
Phase 6 — tsc declaration emission (close the augmentation gap) — DONE 2026-05-28
Phase 5 relocated src/types/* to the top level but the per-module .d.ts files (src/modules/<name>.d.ts) still hand-duplicated the *Options / *Methods / *Events interfaces that the runtime .ts already exports. They existed because the build only copied .d.ts files; it did not emit them from .ts sources.
That hand-duplication also masked a real shipping gap: each runtime module's declare module '../../core/core' { interface SwiperOptions { navigation?: ... } } block never reached consumers. The runtime core/core's SwiperOptions was a separate interface from the public swiper-options.d.ts's SwiperOptions, so augmentations landed on the wrong one. Reproduced before the fix as error TS2353: 'navigation' does not exist in type 'SwiperOptions' against shipped dist/ types.
Resolution: consolidate canonical types into core/core, keep the locked §4.2 augmentation pattern unchanged, and emit declarations via tsc --emitDeclarationOnly so users hit the augmented type. The structural choice was between (A) retargeting declare module paths in 23 modules to point at the hand-maintained public .d.ts, or (B) consolidating type declarations into core/core and re-exporting them through thin shims. Picked (B) — single source of truth, modules unchanged.
Done:
src/types/folder holds the four type-only files renamed from.d.tsto.tsso tsc emits them:options.ts,events.ts,shared.ts,public.ts(theswiper/typesaggregator). Keepssrc/root limited to entry-point.ts/.tsx+ framework-wrapper.d.ts+ CSS.- JSDoc migration: ~500 lines of
Swiper-interface JSDoc moved from the deletedsrc/swiper.d.tsintosrc/core/core.ts's existinginterface Swiperblock and class methods. The runtime signatures (which were more accurate than the hand-maintained ones —getTranslate(): numbervs the oldgetTranslate(): any) stayed; only the docstrings migrated.core/core.tsgrew from ~1100 → ~1600 lines. src/swiper.d.tsdeleted. tsc emitsdist/swiper.d.tsfromsrc/swiper.ts'sexport { default as Swiper, default } from './core/core'— a thin re-export that pulls in the canonical (augmented) types.src/modules/<name>.d.ts× 23 deleted. Each module's*Options/*Methods/*Eventsinterfaces are the runtime declarations insrc/modules/<name>/<name>.ts; tsc emits them todist/modules/<name>/<name>.d.tswith thedeclare module '../../core/core'augmentation block intact.src/modules.d.tsdeleted.dist/modules/index.d.tsis now generated byscripts/build-modules.jsalongside the existingindex.mjs(same module list, parallelexport { default as Navigation } from './navigation/navigation';lines). Importing fromswiper/modulesloads each module's emitted.d.ts, which runs the augmentation block.SwiperModuleFn→SwiperModuleunified. The two near-duplicate types (SwiperModuleinswiper-shared.d.ts,SwiperModuleFnincore/core.ts) collapsed into one canonicalSwiperModuleinsrc/types/shared.ts. Project-wide rename across all 30+ runtime files.core/core.tsre-exports it for ergonomicfrom '../../core/core'imports.scripts/emit-types.jsrunsnpx tsc -p ./tsconfig.emit.json(a standalone config withemitDeclarationOnly: true,outDir: ./dist,rootDir: ./src). Wired intoscripts/build.jsas the first step beforebuildModules.scripts/build-types.jstrimmed from ~150 lines to ~125. Dropped the file-copy logic entirely (tsc handles it). Kept only the framework-wrapper substitution markers (// CORE_EVENTS,// MODULES_EVENTSinswiper-{react,vue,element}.d.ts) and the CSS typing shims. Module events are now extracted directly fromsrc/modules/*/*.tssource (instead of the deleted per-module.d.ts) — more reliable, works in watch mode regardless of emit timing.package.jsonexports updated:./modules→./modules/index.d.ts(was./modules.d.ts),./types→./types/public.d.ts(was./swiper-types.d.ts).tests/dist-types/augmentation.test-d.ts(+ dedicatedtsconfig.dist-types.json) asserts thatnew Swiper('.x', { navigation: { nextEl: '.n' } })type-checks against shippeddist/types after onlyimport { Navigation } from 'swiper/modules'. Wired intonpm testafterbuild:prod. Kept separate fromtests/types/(which compiles against source) because it depends on the build output existing.
Hand-maintained files that remain in src/ root:
- Framework wrappers (
swiper-element.d.ts,swiper-react.d.ts,swiper-vue.d.ts) — they carry build-time substitution markers that tsc can't template. - Their paired
.ts/.tsxruntime files.
Verified:
- Augmentation reaches
dist/consumers (Phase 6's primary goal). The pre-fixTS2353shipping-gap repro now type-checks. npm testgreen: validate + build:prod + contract-test (10/10) + dist-types-test + bundle-size.- Bundle size byte-identical before vs after Phase 6 (verified by stashing the Phase 6 diff and re-running
bundle-size; both report-16985 B (-2.2%) / -2839 B (-1.3%)vs the Phase 0 baseline). All accumulated savings are from Phases 1–5.
Babel fully removed (follow-up, 2026-05-28). Initially scoped out of Phase 6 ("emit step only generates .d.ts; runtime still uses Babel"). Revisited and dropped entirely: at the v14 baseline @babel/preset-env is a no-op (all targets are full ES2022), and @babel/preset-react's only job — the JSX transform in src/react/*.tsx — is now handled by @rollup/plugin-typescript with jsx: 'react'. Removed babel.config.json and all 5 Babel deps (@babel/cli, @babel/core, @babel/preset-env, @babel/preset-react, @rollup/plugin-babel). Verified: runtime bundles byte-identical, React output marginally smaller (TS emits native { ...spread } instead of Babel's _extends helper), zero Babel helpers anywhere in dist/, validate + contract + dist-types all green.
Phase 7 — pre-release type-compat hardening (DONE 2026-06-25)
Goal: prove the v14 type rewrite ships no type-level breaking changes vs published v12, across every public entry point and every moduleResolution mode real users run.
Built tests/consumer-types/ — a consumer simulation that links node_modules/swiper → dist/ (the publish root; npm publish runs from dist/) and type-checks fixtures importing by their real public names (swiper, swiper/modules, swiper/react, swiper/vue, swiper/element, swiper/types, swiper/css, swiper/bundle) under three configs: classic node (node10), node16/nodenext, and bundler. Unlike tests/dist-types/ (bundler-only, raw ../../dist/... imports), this exercises the real exports / typesVersions resolution a downstream project hits. Fixtures carry a few @ts-expect-error lines so the test also fails if option/prop types silently degrade to any. Wired into npm test as consumer-types-test. (The Web Component is fully typed — register, SwiperContainer/SwiperSlide, the HTMLElementTagNameMap global augmentation; the harness exercises it.)
The export surface was byte-identical to v12 (no dropped subpaths), but type resolution had two regressions the bundler-only test could never see:
-
swiper/typesunder classicnode. v12 shippedtypes/index.d.ts; Phase 6 renamed the aggregator totypes/public.d.tsand listed it only inexports(classic resolution ignoresexports). Fix: added atypesentry totypesVersionsinsrc/copy/package.json— restores v12 parity. -
node16/nodenext, package-wide. tsc emits declarations carrying the extensionless relative specifiers from the
.tssources (export * from '../core/core',declare module '../../core/core'). node16/nodenext require explicit extensions, so consumers silently failed to follow internal re-exports anddeclare moduleaugmentation blocks: option/event types collapsed toany,swiper/typeslost its named re-exports, and per-module augmentations (navigation,autoplay, …) never attached. v12 was immune because its hand-written.d.tscarried.d.tsextensions; the Phase 6 switch to tsc emit (extensionless source imports) opened the gap. Fix:scripts/fix-dts-extensions.js, a post-emit build step (runs last inscripts/build.js, after every.d.tsis written) that appends.jsto each relative specifier inimport/export … from, inlineimport('…'), anddeclare module '…'. node16 resolves the.jsto the sibling.d.ts; bundler and classicnodeare unaffected. -
swiper/bundlemodule options (added to Phase 7, 2026-06-25). The bundle build registers every module at runtime, butexports['./bundle'].typespointed at the bareswiper.d.ts(same as core), sonew Swiper(..., { navigation, pagination, … })errored even though the modules are live at runtime. v12 didn't hit this — it inlined all module options into oneSwiperOptions; v14's per-module augmentations only load whenswiper/modulesis imported. Fix:scripts/build-modules.jsnow emits a dedicateddist/swiper-bundle.d.tsthat side-effect-imports./modules/index.js(pulling in every module'sdeclare moduleaugmentation) and re-exportsSwiper;exports['./bundle'].typesand a newtypesVersionsbundleentry point at it.swiper/corestays bare by design (no auto-registration; module options require an explicitswiper/modulesimport, same as the mainswiperentry).
Verified: all three resolution modes type-check cleanly; runtime bundles unaffected (only .d.ts + package.json exports/typesVersions changed); full npm test green.
Phase 8 — SSR runtime parity (DONE 2026-06-26)
Dropping ssr-window (§4.5) replaced its mock window/document/HTMLElement objects with inline typeof guards — but the guards only landed in the obvious feature-detect spots (get-support/get-device/get-browser, swiper-element). The instantiation path still carried unguarded global references, so constructing Swiper in a non-DOM env threw where v12 silently no-op'd (the ssr-window mocks had made those references resolve to harmless stubs).
Important scope note: this was not a Next.js/React/Vue SSR break. The framework wrappers only call new Swiper() inside a mount/layout effect, which never runs server-side — so server rendering just emits static markup and was always fine. The regression only affected imperatively calling new Swiper(...) in pure Node (an unusual but previously-safe pattern).
Three unguarded references, all fixed with typeof guards (zero runtime cost on the browser path):
core.tsconstructor —document.querySelectorAll(params.el)in the multi-element branch (hit whenelis a string). Guarded withtypeof document !== 'undefined' &&.core.tsmount()—initialEl instanceof HTMLElement(HTMLElementis an unguarded global) fired on every instantiation sinceinit: trueis the default. Fixed with an earlyif (typeof document === 'undefined') return false;at the top ofmount(), which also covers the siblingdocument.querySelector.virtual.ts—const tempDOM = document.createElement('div')ran eagerly at module init (so it threw as soon as Virtual was registered, including via the bundle's auto-registration). Made lazy via agetTempDOM()helper. Audited all 23 modules — Virtual was the only one with an init-time DOM reference.
New tests/ssr/ssr.test.mjs (wired into npm test as ssr-test, between consumer-types-test and bundle-size) is the regression guard — the rest of the suite is type-level only and can't catch a runtime SSR throw. It runs in pure Node (asserting window/document are genuinely undefined) and covers: bare imports; every new Swiper() form no-ops gracefully; the bundle-with-all-modules constructor; a per-module loop asserting none touch the DOM at init (init: false); React + Vue renderToString; and web-component register() as a server no-op. No new deps (React/Vue are existing devDeps; @vue/server-renderer ships as a hard dependency of vue). .mjs so it's excluded from tsc type-check, matching dist-contract.test.mjs.
Verified: full npm test green (SSR test 8/8); runtime bundles still net smaller (the guards are tiny and swiper-core actually shrank).
Confidence: H = high (just do it), M = medium (verify with a small spike), L = low (leave a TODO, do in v15).
| # | Cleanup | File(s) | Confidence | Notes |
|---|---|---|---|---|
| 1 | Remove ssr-window dependency |
everywhere | H | Replace with typeof document !== 'undefined' guards. Deletes one runtime dep entirely. |
| 2 | Drop support.smoothScroll |
src/shared/get-support.mjs |
H | Universal at baseline. |
| 3 | Simplify support.touch to inline 'ontouchstart' in window |
src/shared/get-support.mjs |
H | DocumentTouch was removed from browsers years ago. May not even need a separate util — inline at the 2–3 call sites. |
| 4 | Delete browser.needPerspectiveFix (Safari < 16.2 workaround) |
src/shared/get-browser.mjs |
H | Below baseline. Also lets the file shrink significantly. |
| 5 | Keep browser.need3dFix for iOS WebView |
same | — | Still a real bug on iOS WebView. |
| 6 | Trim iPad-on-MacIntel screen-size table | src/shared/get-device.mjs |
M | Verify whether Safari on modern iPadOS reports correctly now. If yes, delete the 12-entry screen-size array. If not, keep but document why. |
| 7 | Replace custom DOM helpers in src/shared/utils.mjs |
utils.mjs (~408 lines) |
H | elementChildren → [...el.children].filter(c => c.matches(sel)). elementOffset → getBoundingClientRect(). elementIndex → [...parent.children].indexOf(el). elementStyle → inline getComputedStyle(el).getPropertyValue(prop). elementOuterSize → getBoundingClientRect() + computed margins. Likely halves the file. |
| 8 | Audit nextTick / now / extend / deleteProps helpers |
utils.mjs |
M | Date.now() is universal. Object.assign covers extend for shallow cases. nextTick is a setTimeout(fn, delay) wrapper — keep or inline depending on call-site count. |
| 9 | Verify core/modules/resize and core/modules/observer are thin wrappers over ResizeObserver / MutationObserver |
src/core/modules/{resize,observer}/ |
M | Both ~67 lines. Already plausible they're modern; just confirm no dead polyfill paths. |
| 10 | Collapse dual touch+pointer event registration | src/core/events/index.mjs lines 19–30 |
L | Defer to v15. Currently registers both touchstart+pointerdown etc. with runtime guards in onTouchStart/onTouchMove. Pointer-only would shrink three handlers significantly but iOS WebView edge cases need their own investigation cycle. |
| 11 | process-lazy-preloader modernization |
src/shared/process-lazy-preloader.mjs |
M | Check if it can use loading="lazy" + IntersectionObserver natively. |
| 12 | Audit create-shadow.mjs and create-element-if-not-defined.mjs |
src/shared/ |
M | Custom element helpers — may have grown legacy branches. |
Out-of-scope cleanups (named so future-me doesn't accidentally drag them in):
- Any refactor of the touch event state machine itself.
- Any change to CSS class naming or DOM structure.
- Any change to the public options surface.
Zero runtime breaking changes for v14. This is non-negotiable. Constraints:
- Every option name, default, and shape stays the same.
- Every event name, payload, and timing stays the same.
- Every public method signature stays the same.
- Module imports (
swiper/modules,swiper/react, etc.) resolve identically.
Type-level breaking changes are allowed but should be minimized. If a user had any-typed access to internals, they may get a new error. Document these in the migration guide.
If during migration we discover a runtime bug that requires a behavior change to fix, file it separately and ship it as a minor on v12 first if possible. Don't tangle bug fixes into the rewrite.
- Contract test (Phase 0): imports compiled
dist/and exercises full public API. Should pass on v12 today; should keep passing through every v14 phase. - Type tests: a
tests/types/directory with.tsfiles that import Swiper + modules and assert thatswiper.navigation.update()etc. type-check. Usetsdorexpect-type. Critical for validating the augmentation pattern works at the user surface. - Consumer type-resolution tests (Phase 7):
tests/consumer-types/imports the built package by its public subpath names and type-checks fixtures (core,swiper/types, React, Vue, Web Component) under classicnode,node16/nodenext, andbundler. Catchesexports/typesVersionsresolution regressions the bundler-onlydist-typestest misses. Part ofnpm test. - SSR runtime test (Phase 8):
tests/ssr/ssr.test.mjsruns in pure Node (no DOM) and asserts imports + everynew Swiper()form + React/VuerenderToString+ web-componentregister()never throw. The only runtime test in the suite (everything else is type-level); guards thessr-windowremoval against reintroduced unguardeddocument/HTMLElementreferences. Part ofnpm test. - Visual regression: run the existing
demos/against v12 dist and v14 dist; diff screenshots. Catches subtle CSS/DOM regressions. - No new unit tests required for the rewrite itself — the goal is no behavior change.
The user has flagged that this likely spans multiple sessions. Rough ordering:
- Phase 0 (infra) — 1 session
- Phase 1 (shared) — 1 session
- Phase 2 (core) — 2–3 sessions (largest single piece)
- Phase 3 (modules) — 1 session per ~5 modules ≈ 5 sessions
- Phase 4 (wrappers) — 1 session each = 3 sessions
- Phase 5 (cleanup/release) — 1 session
- Phase 6 (tsc declaration emission) — 1 session
Realistic total: ~13–16 working sessions. Keep v14 branch alive across sessions; never merge to master until all phases land.
Captured so future-me doesn't re-litigate. Each entry: date — decision — reason.
- 2026-05-27 — TS pattern is declaration merging (option A), not generic
Swiper<TModules>. Reason: better DX at call sites, matches ecosystem conventions. - 2026-05-27 — Baseline is last 2 years evergreen. iOS/Android detection kept but simplified.
- 2026-05-27 — Core decomposition (prototype → composition) is deferred to v15. v14 keeps
Object.assign(Swiper.prototype, ...)intact. - 2026-05-27 — Skip v13 ("unlucky number"). Next major is v14.
- 2026-05-27 —
ssr-windowis removed in v14. SSR support continues via inlinetypeofguards. - 2026-05-27 — Phase 6 added. Phase 5's relocated per-module
.d.tsfiles duplicate runtime-.tsinterfaces and silently mask the module-augmentation shipping gap; Phase 6 enablestsc --emitDeclarationOnlyto make TS sources truly canonical for shipped types. - 2026-05-28 — Phase 6 close-the-gap strategy is consolidate, don't retarget. Module augmentations stay on
declare module '../../core/core'(per §4.2); the user-facingSwiper/SwiperOptions/SwiperEventsare now re-exported fromcore/corerather than being separate hand-maintained interfaces. Reason: single source of truth, modules unchanged, simplerdist/shape. The alternative (retarget 23 modules'declare modulepaths to'../../swiper-options.d.ts'etc.) would have preserved more files unchanged at the cost of two parallelSwiperOptionsidentities and per-target augmentation overhead. - 2026-05-28 — Hand-maintained type-only files moved into
src/types/(options.ts,events.ts,shared.ts,public.ts), renamed.d.ts→.tsso tsc emits them. Keepssrc/root limited to entry points + framework-wrapper substitution.d.ts+ CSS. Did NOT splitSwiper-interface JSDoc out ofcore/core.ts— interface and class belong together; the file growth to ~1600 lines is acceptable. - 2026-05-28 —
src/modules.d.tseliminated entirely, replaced bydist/modules/index.d.tsgenerated alongsidedist/modules/index.mjsinscripts/build-modules.js(same module list, parallel re-export lines). Reason: the hand-maintained list duplicates the build config; auto-generation eliminates drift risk. - 2026-05-28 —
SwiperModuleFn(internal, incore/core.ts) andSwiperModule(public, inswiper-shared.d.ts) unified toSwiperModuleproject-wide. They were structurally identical; the dual naming was historical drift. - 2026-05-28 — tsc emit strategy is emit everything to
dist/, hand-maintained files overwrite after.scripts/build-types.jswrites the framework-wrapper substituted.d.tsfiles at the samedist/swiper-{react,vue,element}.d.tspaths AFTER tsc emit; later writes win. Simpler than maintaining a tsconfigexcludelist of wrapper entry files. - 2026-05-28 —
tests/dist-types/is a separate test directory with its owntsconfig.dist-types.json, excluded from the maintsconfig.json. Reason: it imports fromdist/so it must run AFTERbuild:prod; including it in the main type-check would fail on a clean tree. - 2026-06-25 — Added Phase 7 consumer type-resolution tests (
tests/consumer-types/), checking every public entry point under classicnode/node16/bundler. Reason: the existingdist-typestest only coversbundlervia rawdist/paths and missed two real regressions. Same exclusion rationale astests/dist-types/— its own per-mode tsconfigs, excluded from the maintsconfig.json. - 2026-06-25 — Ship
.d.tswith explicit.jsextensions on relative specifiers, applied via a post-emit rewrite (scripts/fix-dts-extensions.js) rather than changing source import style. Reason: node16/nodenext require extensions; rewriting at emit avoids churning every source import to.jsand keeps source ergonomic. Also addedtypestotypesVersionsto restoreswiper/typesresolution under classicnode. Both restore v12 parity; neither touches runtime bundles. - 2026-06-25 —
swiper/bundlegets dedicated augmentation-loading types (dist/swiper-bundle.d.ts, generated inscripts/build-modules.js) instead of reusing the bareswiper.d.ts. Reason: the bundle auto-registers every module at runtime, so its types must expose module options/events/methods without aswiper/modulesimport — the v14 augmentation split broke that.swiper/coredeliberately stays bare (it mirrors the main entry's no-auto-register semantics). - 2026-06-26 — Phase 8 restores SSR runtime parity lost when
ssr-windowwas removed. The instantiation path (new Swiper(...)in pure Node) threw on unguardeddocument/HTMLElementglobals where v12's ssr-window mocks had made it a silent no-op. Fixed withtypeofguards incore.ts(constructor +mount()) and lazy DOM creation invirtual.ts. Framework SSR (Next.js/React/Vue) was never affected — wrappers instantiate only in client mount effects. Addedtests/ssr/ssr.test.mjsas the suite's only runtime test, since the type-level tests can't catch a runtime throw. Reason: "didn't throw in Node" is part of the implicit v12 contract; restore it and lock it with a test.
- Build pipeline: stay on Rollup with
@rollup/plugin-typescript, or switch totsc+rolluptwo-step? React JSX handling is the main wrinkle. Recommendation: keep Rollup, add@rollup/plugin-typescript, drop Babel except for JSX in the React wrapper. tsconfig.jsontarget: confirmedes2022? Lets us keep?.,??, top-levelawait(not used today), class fields. Aligns with the browser baseline.- Module pre-bundle for
swiper/modules: today every module is a separate file. With TS we might want to also offer a tree-shake-friendly barrel. Confirmpackage.jsonexportsshape stays the same. - Vue version support: are we Vue 3-only, or does the Vue wrapper still need Vue 2 codepaths? (Currently checking
src/vue/deferred to Phase 4.) - Custom Element TS: any decision on whether to ship
.d.tsfor the custom element's attribute names (e.g., for IDE attribute autocomplete in HTML)? Out of scope but worth tracking.
For a future session picking this up: start by re-reading §3 (baseline), §4.2 (the locked pattern), and §11 (decisions). Then check the current state of master against this plan — what's been done, what's next. The phase ordering in §6 is the entry point.