Skip to content

feat(migrate): cover the remaining v9 breaking changes - #31355

Merged
ShaneK merged 4 commits into
major-9.0from
chore/migrations
Aug 14, 2026
Merged

feat(migrate): cover the remaining v9 breaking changes#31355
ShaneK merged 4 commits into
major-9.0from
chore/migrations

Conversation

@ShaneK

@ShaneK ShaneK commented Aug 13, 2026

Copy link
Copy Markdown
Member

Issue number: internal


What is the current behavior?

Currently, @ionic/migrate covers 15 of the breaking changes in the v9 upgrade guide. The Angular moduleResolution change and @ionic/core's new exports field aren't in there, and neither are about a dozen others.

There's also a gap for vanilla apps: detectFrameworks only knows @ionic/angular, @ionic/react, and @ionic/vue, so a vanilla app prints "No @ionic/{angular,react,vue} dependency found" and exits. Every migration marked framework: 'core' is unreachable for the apps it was written for.

What is the new behavior?

This PR adds 14 migrations, taking the registry to 29 (12 auto-fix, 17 report-only). The full table is in the new packages/migrate/docs/v9.md. The README is version-agnostic now, so the next major gets its own page instead of a rewrite.

We also detect @ionic/core as a framework, so vanilla apps run the core migrations and get their pin bumped. We gate the version check on the binding package when there's one, because an app on a v9 binding with a stale @ionic/core pin is already migrated, and taking the lowest major would re-run the single-shot import rewrite and corrupt it.

Does this introduce a breaking change?

  • Yes
  • No

Other information

Note: this PR is based off of the end result of ionic-docs#4620. Ten of the docs anchors these migrations link to only exist on that branch.

Two v9 changes stay uncovered on purpose, with the reasoning in docs/v9.md: React Router's /* suffix for nested routes, and which components Angular 22's OnPush default affects. We do flag the Angular 22 version itself.

@ShaneK
ShaneK requested a review from a team as a code owner August 13, 2026 20:05
@ShaneK
ShaneK requested a review from OS-jacobbell August 13, 2026 20:05
@vercel

vercel Bot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ionic-framework Ready Ready Preview Aug 14, 2026 9:17pm

Request Review

@codeCraft-Ritik codeCraft-Ritik 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.

Really thorough PR, @ShaneK. A few things I appreciated while going through this:

The vanilla app gap fix — detectFrameworks() excluding core meant every framework: 'core' migration was dead code for the apps it was written for. Adding @ionic/core to the detection map and introducing sourceMajor() is the right solution. The binding-over-core priority prevents the corruption scenario where a v9 Angular app with a stale @ionic/core@^8 pin would re-select destructive migrations.

core-modal-handle's brace-balancing — Scoping handleBehavior checks to the enclosing options object (via enclosingObject()) is a thoughtful touch. Without it, one opted-out sheet modal would silence every other sheet in the file. The Vue .vue test case (template opt-out shouldn't suppress script block findings) is a great edge case to cover.

core-form-structure's rule ordering — Putting the slot rules before the broader wrapper rule so the guide's own example selector (ion-input .input-wrapper .native-wrapper [slot="start"]) gets the .input-start answer instead of .input-control shows attention to the developer experience of the report output.

ScanMatch union type — Letting a single migration emit per-finding docsUrl overrides is a clean abstraction. core-select-events linking ionChange findings to one anchor and selected role findings to another is exactly the kind of granularity that saves someone's debugging time.

Minor note: the core-textarea-height scan for 56px scoped to files mentioning ion-textarea is a pragmatic tradeoff — 56px is too common to flag globally, but it does mean a stylesheet that sets the height on a selector like .my-textarea-wrapper without the tag name would be missed. Probably fine for real-world usage though.

Solid work taking the registry to 29 with full fixture-backed tests. 👏

major-9.0 removed the IONIC_V9_VERSION dev build pin in favor of the
numeric major target. Resolved react-deps.ts to keep the new React 18
bumps alongside the numeric Ionic targets, and engine.test.ts to keep
the sourceMajor import without versions.js.

Also updated core-deps.ts and two migrations.test.ts references, which
git auto-merged clean but still pointed at the deleted module.

@thetaPC thetaPC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, just grammar

Comment thread packages/migrate/docs/v9.md Outdated
Comment on lines +3 to +4
What `npx @ionic/migrate` covers for the Ionic 8 to Ionic 9 upgrade, and what it
leaves to you. The full list of breaking changes, with before/after examples, is

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The first sentence sounds a bit weird to me. Maybe: "This page covers what npx @ionic/migrate handles for the Ionic 8 to Ionic 9 upgrade, and what it leaves to you."

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, that was a fragment. Done!

Comment thread packages/migrate/docs/v9.md Outdated

## Coverage

| Change | Framework | Mode |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Might be worth creating separate tables for each framework and each table would have it's own title. It would be beneficial to devs who just want to review specific frameworks that are relevant to them.

### Angular

"table for angular"

### React

....

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good call! Split into Angular, React, Vue, and All frameworks, and dropped the Framework column since the heading covers it now. There's a line up top pointing people at their framework's table plus the core one, since the core changes apply on top.

* works, and the rewrite is structural (the entry moves from a module's
* `imports` to its `providers`, with any `forRoot()` config).
*
* See https://ionicframework.com/docs/updating/9-0#ionicmodule-deprecation

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
* See https://ionicframework.com/docs/updating/9-0#ionicmodule-deprecation
* Refer to https://ionicframework.com/docs/updating/9-0#ionicmodule-deprecation

@ShaneK ShaneK Aug 14, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch! Done

* `tsconfig.json` carries `//` comments, which are valid JSONC but not JSON, and
* round-tripping through `JSON.parse`/`stringify` would silently drop them.
*
* See https://ionicframework.com/docs/updating/9-0#module-resolution

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
* See https://ionicframework.com/docs/updating/9-0#module-resolution
* Refer to https://ionicframework.com/docs/updating/9-0#module-resolution

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done!

* Pinned with a tilde: Angular's peer ranges are narrow (18 accepts `>=5.4 <5.6`),
* and `^5.4.0` resolves to 5.9.x, which nothing below Angular 20 accepts.
*
* See https://ionicframework.com/docs/updating/9-0#typescript

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
* See https://ionicframework.com/docs/updating/9-0#typescript
* Refer to https://ionicframework.com/docs/updating/9-0#typescript

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done!

detect(ctx) {
return scanLines(ctx, SOURCE_GLOBS, (line) =>
SWIPE_BACK.test(line)
? 'in React and Vue, swipeBackEnabled is read once at outlet mount now. If you toggle it at runtime, use the swipeGesture property'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
? 'in React and Vue, swipeBackEnabled is read once at outlet mount now. If you toggle it at runtime, use the swipeGesture property'
? 'In React and Vue, swipeBackEnabled is read once at outlet mount now. If you toggle it at runtime, use the swipeGesture property.'

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This one's the same as the toolkit finding: the details print as annotations after the path:line - prefix, and none of the ~30 in the registry ends in a period, so I'd rather keep them uniform.

* common enough that flagging it everywhere would be noise. A stylesheet that
* never names the element is missed.
*
* See https://ionicframework.com/docs/updating/9-0#minimum-height-change

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
* See https://ionicframework.com/docs/updating/9-0#minimum-height-change
* Refer to https://ionicframework.com/docs/updating/9-0#minimum-height-change

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done!

findings.push({
filePath,
line: i + 1,
detail: 'ion-textarea is 72px tall in md mode now, not 56px. Update or override this value',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
detail: 'ion-textarea is 72px tall in md mode now, not 56px. Update or override this value',
detail: 'ion-textarea is 72px tall in md mode now, not 56px. Update or override this value.',

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I've left the period off here for the same reason as the toolkit finding, the details print as annotations and none of the ~30 in the registry ends in one.

docsUrl: `${V9_DOCS}#route-definition-changes`,
};
const CHILDREN_REMOVED: ReportedChange = {
detail: `Route content passed as children no longer renders. Move it into the "element" prop`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
detail: `Route content passed as children no longer renders. Move it into the "element" prop`,
detail: `Route content passed as children no longer renders. Move it into the "element" prop.`,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Leaving this one as-is too, same reason as the toolkit thread.

Comment thread packages/migrate/README.md Outdated
Comment on lines +46 to +47
Each major upgrade has its own page, listing every change the tool covers, which
kind it is, and what it leaves to you:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sounds weird to me, maybe:

Suggested change
Each major upgrade has its own page, listing every change the tool covers, which
kind it is, and what it leaves to you:
Each major upgrade has its own page, listing every change the tool covers, how it is classified, and the changes left for you to make manually:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Reworded, though I went with "whether it is auto-fixed or report-only" since those are the two names the section right above defines.

@ShaneK
ShaneK merged commit 37525a2 into major-9.0 Aug 14, 2026
52 checks passed
@ShaneK
ShaneK deleted the chore/migrations branch August 14, 2026 21:25
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.

3 participants