Repository navigation
Conversation
The transforms were only reachable through a classic V1 addon shell, which does not
function under Embroider + Vite: `included()` pulled in `ember-cli-htmlbars@6`, which
reads `ember.absolutePaths.templateCompiler`, an API `ember-source` 7 no longer exposes.
`ember-cli-htmlbars` was never required by any code here; it was a dependency only.
Split the repo into two packages:
- `babel-plugin-ember-formatjs` holds both transforms and has no Ember dependencies.
Vite apps install it and wire it into `babel.config.*` themselves, because Embroider
v2 has no hook that can add a Babel plugin or template transform to the host app.
- `ember-formatjs` keeps the classic ember-cli wiring and depends on the plugin, so
`ember install ember-formatjs` still needs no app changes. It no longer depends on
`ember-cli-htmlbars`, `ember-cli-babel`, `broccoli-stew`,
`calculate-cache-key-for-tree` or `ember-cli-version-checker`, none of which it
required.
Transform fixes needed to serve both pipelines:
- read `env.filename` first, falling back to the legacy `meta.moduleName`.
`babel-plugin-ember-template-compilation` sets both from v2 on, so one source works
under v2, which `ember-cli-htmlbars` uses, and v4, which Embroider uses.
- bind the `t` helper import via `env.meta.jsutils` so the rewrite compiles in
strict-mode `.gjs`/`.gts` templates, where a bare `{{t}}` is not in scope.
- only rewrite `intl.formatMessage()` when the first argument is an object literal
carrying `defaultMessage` or `id`, and never inside `node_modules`. Under Vite,
`ember-intl`'s own sources run through the same Babel pass, and its internal
`formatMessage(descriptor, values)` calls were being rewritten, leaking the raw id
pattern into translation keys.
- read the consuming app's name lazily, since the plugin is now required from a
`babel.config.*` in a process that may not be rooted at a package.
- build the rewritten argument list rather than assigning into it by index, which left
holes that printed as `intl.t('key', )`.
Tests: the template transform had no coverage at all. It now has 23 tests, every
integration case running against both v2 and v4 of the template compiler, which produce
byte-identical output. The JS transforms gain 8 tests covering what must not be rewritten.
The existing fixtures are unchanged and still pass.
Translation keys are unaffected. Extraction and the transform both hash message content
with `interpolateName` and ignore the file path, so the same message keys the same way in
either pipeline.
Also bumps all dependencies, drops the unused dummy-app devDependencies, and runs CI on
Node 22, 24 and 26 plus a job pinning `@babel/core` 7 to keep the lower bound of the peer
range honest.
caced70 to
3eaf6ac
Compare
Three gaps in the split:
- Both packages declare MIT but shipped no LICENSE file. npm only includes one from
the package's own directory, so each now carries a copy.
- `ember-formatjs` had no tests at all. Its whole job is the classic registry and babel
wiring, which is exactly the part this change is promising not to break. 8 tests now
cover the registry entry, the parallel-build descriptor ember-cli rebuilds workers
from, `baseDir` resolving inside the package rather than the consuming app, options
merging, and the host babel plugin being added exactly once.
- `publish.yml` and `plan-release.yml` still ran on Node 18, which no longer satisfies
the engines range. Both now run on 24, matching CI.
The added sections were hard-wrapped while the rest of the file keeps one line per paragraph, so the seams showed. Also replaces the Vite options example, which was a fragment with dangling commas rather than something you could paste.
e14d103 to
f7dbc2a
Compare
|
@BlueCutOfficial is there a chance we can land this 🙏🏻 |
- Revert the `.release-plan.json` edit. `plan-release.yml` treats any push that
touches that file as a release and skips opening the Prepare Release PR, so
merging it would have held up the very release this is for. release-plan
rewrites the file on its next run anyway.
- `@babel/helper-plugin-utils` 8 is ESM-only, peers on `@babel/core` ^8 and needs
Node 22.18, so every Babel 7 consumer got an unmet peer warning. That is every
classic app, since ember-cli-babel runs Babel 7. Back to 7.29.7, which runs under
both 7 and 8.
- Widen the `ember-intl` peer range to 8 and 9. Both still ship `helpers/t` and
`helpers/format-message`, which is all the transforms rely on.
- Node floor is `^20.19.0 || >=22.12.0`. `@formatjs/ts-transformer` 4 is ESM-only,
but `require(esm)` is unflagged from 20.19.0 as well as 22.12.0; the suite passes
on 20.20.2.
- The `@babel/core 7` job never ran Babel 7: `pnpm add -w` does not reach the
plugin package, where pnpm installs the peer itself, nor babel-plugin-tester. It
now overrides `@babel/core` workspace-wide, asserts the version the tests load,
and runs on Node 20.19.0 so it holds the lower end of both declared ranges.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`jsutils` is present under v2 as well as v4, so every loose `.hbs` template in a
classic app was being compiled with `import t from "ember-intl/helpers/t"` and a
`scope`, where it used to resolve `{{t}}` through the resolver. That is a change to
classic output that nothing tested.
Bind the import only when `env.strictMode` is set, which both template compilers set
for `precompileTemplate(..., { strictMode: true })` and for the `template()` form
`content-tag` emits for `.gjs` / `.gts`. Loose templates get a bare `{{t}}` again,
which under Embroider is bound by the loose-mode resolver transform like any
hand-written `{{t}}`.
Tests cover the `<template>` form and assert that loose templates gain no import.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The docs said Embroider has no hook through which an addon can add a Babel plugin or a template transform. `@embroider/compat` has one: `babelCompatSupport()` and `templateCompatSupport()` pull in what v1 addons register. The reason to wire the plugin package directly is that it keeps the classic addon out of the build and works without `@embroider/compat`, so say that, and say the compat path is untested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Hi @aklkv, just letting you know that I saw this PR, and I thank you for your work on this. The PR size just make it a bit hard for me to review with the little time I have, though, and there might be room to make this easier to process. For instance, the most complicated commits mix seveal concepts together (moving code + changing the CI matrix), and there are also history problems like re-fixing the node version afterward in the new packages instead of doing it right at package creation, which add noise that I need to look at. It would probably be easier to get this land with smaller PRs/commits introducing the new concepts step by step (e.g. 1 - fix any dep issue you spotted in the v1 addon, 2 - create the monorepo with the v1 addon as the only package in it, 3 - move the babel plugin into its own package, 4 - create additional tests, etc). This type of very iterative process makes it way easier for maintainers with very little time available 🙏 |
|
Thanks @BlueCutOfficial, fair point. I've split it into smaller PRs:
#17 to #20 are independent of each other. The drafts build on them, so I'll rebase each one as the earlier ones land. It's probably worth cutting a release after #17 to #20 and before #23 moves the files, so release-plan still attributes them to ember-formatjs. |
Closes #15.
Problem
The transforms are only reachable through the classic v1 addon. Under Embroider + Vite with
ember-source7, that addon breaks in two places:ember-cli-htmlbars@6is independencies, so ember-cli instantiates it as a nested addon. Itsincluded()readsember.absolutePaths.templateCompiler, whichember-source7 no longer exposes, and the build fails before any template is compiled. Nothing in this package requiresember-cli-htmlbars.env.meta.moduleName ?? env.moduleName.babel-plugin-ember-template-compilationv4 sets neither of these, onlyenv.filename, so every template throws.Only three of the eight runtime dependencies are required by the source:
@babel/helper-plugin-utils,@formatjs/ts-transformerandember-cli-babel-plugin-helpers. The other five (ember-cli-htmlbars,ember-cli-babel,broccoli-stew,calculate-cache-key-for-tree,ember-cli-version-checker) are dropped.Change
The repo becomes a pnpm workspace with two packages:
babel-plugin-ember-formatjsbabel.config.*ember-formatjsember install ember-formatjsstill needs no app changesVite apps can use the transforms without a v1 addon in the build and without
@embroider/compat.@embroider/compatcan pick up a v1 addon's Babel and template plugins, butember-formatjsisn't tested that way.Transform changes
env.filenamefirst, thenenv.meta.moduleNameandenv.moduleName. Template compiler v2, whichember-cli-htmlbars@6uses, setsfilenameand a top-levelmoduleName. v4 sets onlyfilename.{{format-message}}to{{t}}in.gjs/.gtsfailed with "not in scope: t". In strict mode the transform now importstthroughenv.meta.jsutils.bindImport. Loose.hbstemplates still get a bare{{t}}, so classic output is unchanged.ember-intl's own source goes through the app's Babel pass, and its internalintl.formatMessage(descriptor, values)calls were being rewritten. The replacer now fires only when the first argument is an object literal withdefaultMessageorid, and never insidenode_modules.package.jsonname was read at module load. It is now read on first use, and a missing file is tolerated.intl.t('key', ).Translation keys don't change. The existing fixtures pass unmodified after the
@formatjs/ts-transformer3 → 4 bump. The default[sha512:contenthash:base64:6]pattern hashes message content only, so neither the file path nor the build pipeline affects a key.Compatibility
^20.19.0 || >=22.12.0.@formatjs/ts-transformer4 is ESM-only and is loaded withrequire(), which works without a flag from 20.19.0 and from 22.12.0.@babel/core7 or 8.ember-intl7, 8 or 9.Tests
49 tests, up from 6.
CI runs lint and tests on Node 22, 24 and 26 with Babel 8. A lower-bounds job runs on Node 20.19.0 with Babel 7. It forces Babel 7 through a workspace-wide override, with a step that asserts Babel 7 is what the tests load.
For maintainers
.release-plan.jsonis untouched. Both packages are at 0.1.3. The engines range narrows, so I'd label thisenhancement, which publishes both as 0.2.0. Thebabel-plugin-ember-formatjsname is unclaimed on npm, so the first publish makes theNPM_TOKENaccount its owner.Not covered
No fixture app builds a classic or Vite app end to end. The Vite path has run in our app against a vendored copy of these transforms. The strict-mode-only import (transform change 2) came after that run.