Skip to content

Preparation before removing babel plugins - adding import extensions and displayName manually#2580

Open
joyenjoyer wants to merge 3 commits into
masterfrom
pr1-esm-safe-source-prep
Open

Preparation before removing babel plugins - adding import extensions and displayName manually#2580
joyenjoyer wants to merge 3 commits into
masterfrom
pr1-esm-safe-source-prep

Conversation

@joyenjoyer

@joyenjoyer joyenjoyer commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR prepares the removal of Babel and Babel plugins. It writes two things into the source that Babel plugins inject today: explicit relative-import extensions and static displayNames. It also adds minimal webpack config so our own toolchains resolve the new specifiers.

No transpiler change. Babel still runs, with the same plugins. The plugins are idempotent. They skip a specifier already ending in .js. They skip a class that already has a displayName. So the compiled es/ and lib/ output stays semantically identical to master.

This keeps master releasable. It also makes the later SWC safe.

This is PR1 of a 3-PR stack:

  • PR1 (this one) — source prep, no transpiler change. Non-breaking.
  • PR2 — swap babel-loaderswc-loader in the Cypress + docs webpack builds. Library build still Babel. Nothing published changes.
  • PR3 — ESM-only library build on SWC, and remove Babel + its plugins. The breaking PR.

What changed (3 commits)

  1. add explicit import extensions to relative specifiers — appends .js (or /index.js for directory imports) to relative import / export ... from specifiers. Reproduces babel-plugin-add-import-extension. Relative specifiers only. Bare/package and type-only imports are untouched.
  2. add static displayName to component classes — adds static displayName = '<ClassName>' to React class components that lack one. Reproduces babel-plugin-add-displayname-for-react. It is idempotent: it skips classes that already have one, and any class without a render method. This includes the __docs__ app. Its Babel config (packages/__docs__/babel.config.js) uses @instructure/ui-babel-preset, which runs add-displayname-for-react unconditionally. So Babel was already injecting displayName into the docs' class components at build time. Writing it into the source keeps that behavior identical once Babel is removed in PR3.
  3. resolve .js specifiers to TypeScript source in webpack — adds resolve.extensionAlias (.js.ts/.tsx) to the Cypress config and the shared ui-webpack-config. This lets webpack builds that bundle the .ts/.tsx source directly (Cypress, docs) resolve the new .js specifiers. It is a consequence of the source change, so it lives here.

What is intentionally NOT in this PR

  • Removing the Babel plugins. They stay until PR3. Removing them here would change build output. It would also break the ESM output of generated files (ui-icons/src/generated, ui-themes tokens). Those are generated/gitignored, so the source edits can't cover them. Generator updates will be in a later PR.

Compiled-output deltas vs master

All behavior-preserving:

  • ESM (es/) — cosmetic codegen only. Semantically identical. The possible differences are:
    • Quote style — a specifier emitted with single quotes vs double quotes (e.g. "./useStyle.js"'./useStyle.js').
    • Blank lines — an added or removed empty line between statements.
    • Comment placement — a leading comment attached to a slightly different line.
    • Line wrapping — a long import/export wrapped across lines vs kept on one line.
    • Trailing commas / whitespace — minor punctuation or indentation noise from the codegen path.
  • CJS (lib/) — the added .js specifiers, plus the require binding names Babel derives from them. Behavior-preserving. They disappear once PR3 drops the lib/ build.

How to test?

  • Run commands (bootstrap, test, dev)
  • Spot check file in es folders, compare it to latest master
  • Use Balazs' diff tool or another comparison script but keep in mind there will be changes (read the Compiled-output deltas vs master above), it is only an issue if the two files are not semantically identical

Appends .js/index.js to relative imports across the source so the output resolves under native ESM.
Preserves component identity without Babel's add-displayname plugin.
The explicit .js relative-import extensions added for native ESM must still
resolve when webpack bundles the .ts/.tsx source directly (Cypress component
tests and the docs app). Add resolve.extensionAlias mapping .js->.ts/.tsx to
the Cypress and ui-webpack-config configs so the source prep does not break
those builds. No transpiler change.
@joyenjoyer joyenjoyer requested a review from Copilot June 4, 2026 12:13

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 wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

@joyenjoyer joyenjoyer self-assigned this Jun 4, 2026
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://instructure.design/pr-preview/pr-2580/

Built to branch gh-pages at 2026-06-04 12:16 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

github-actions Bot pushed a commit that referenced this pull request Jun 4, 2026
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

Visual regression report

No changes.

Status Count
Unchanged 32
Changed 0
New 0
Removed 0

📊 View full report

Baselines come from the visual-baselines branch. They refresh on every merge to master.

@joyenjoyer joyenjoyer requested review from balzss and matyasf June 4, 2026 12:55
Comment on lines +81 to +88
// Source carries explicit `.js` extensions on relative imports (added for
// native-ESM library output). When bundling `.ts`/`.tsx` source directly,
// map a `.js` specifier back to its TypeScript source.
extensionAlias: {
'.js': ['.ts', '.tsx', '.js'],
'.jsx': ['.tsx', '.jsx'],
'.mjs': ['.mts', '.mjs']
},

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Will this also be need to be set for end users?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We only need this because Cypress and the docs build straight from our .ts source, so the new .js import extensions have to be pointed back at the real .ts/.tsx files. Consumers never hit this since they use the built es/ and lib/ output where those .js files actually exist.

@joyenjoyer joyenjoyer requested a review from matyasf June 10, 2026 11:32

@balzss balzss 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.

overall looks and works well, great work! some minor comments:

  • you added displaynames for the docs package components but didn't add the extensions for the imports there. is that intentional?
  • you added explicit type resolution for the cypress config but not for vitest. i'm not sure it's bad if it's implicit but worth thinking about
  • the commit msg scope is a bit misleading. they are build(swc) but swc migration was not done in those commits

@joyenjoyer

joyenjoyer commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

overall looks and works well, great work! some minor comments:

  • you added displaynames for the docs package components but didn't add the extensions for the imports there. is that intentional?
  • you added explicit type resolution for the cypress config but not for vitest. i'm not sure it's bad if it's implicit but worth thinking about
  • the commit msg scope is a bit misleading. they are build(swc) but swc migration was not done in those commits
  1. yes, this is intentional. Babel plugins added displayname to components in the docs package but no extensions. I intend to keep it this way.
  2. Vite already des .js->.ts rewrite natively, cypress project uses webpack that has to be configured manually
  3. I will change that, thanks

@balzss

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.

4 participants