Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: Vite support, template coverage, and ember-cli decoupling - #432

Open
aklkv wants to merge 6 commits into
ember-cli-code-coverage:masterfrom
aklkv:feat/modernize
Open

feat: Vite support, template coverage, and ember-cli decoupling#432
aklkv wants to merge 6 commits into
ember-cli-code-coverage:masterfrom
aklkv:feat/modernize

Conversation

@aklkv

@aklkv aklkv commented Feb 23, 2026

Copy link
Copy Markdown

Explorations for #431 — continues the v2 addon migration and takes it to a working state across classic, Embroider, and Vite builds.

What #431 asked for

The issue is really about decoupling from ember-cli, not just "add Vite". Status against the specific asks in that thread:

  • Middleware usable directly by testem, not via the ember-cli hook — done. coverageMiddleware and viteTestemMiddleware are plain exports from ember-cli-code-coverage/testem, wired into testem.cjs with no ember-cli involvement.
  • Commands working without ember-cli — done. New coverage-merge bin runs the merge standalone, with optional --root/--config.
  • Composable subpath entry points (gossi's /istanbul, /vite, /testem, /babel) — done, as /core, /vite, /testem, /babel, plus /glimmer and /merge. Named /core to match the internal directory, consistent with every other subpath here.
  • Native V8 coverage instead of Istanbul — not attempted. It's raised as an open question in the thread and nobody committed to it; still Istanbul-based.
  • vitest browser mode / ember-vitest — untouched, still experimental upstream.

Regressions fixed from the original modernize commit

These were broken on the branch before this work, not by it:

  • Embroider reported no coverage at all. The default exclude changed from */node_modules/**/* to **/node_modules/**/*, and Embroider ≥3.1 rewrites the app into node_modules/.embroider/rewritten-app/, so the app's own files were all excluded. The committed snapshot proves it — it contained one entry, this addon's own file, and none of the app's.
  • ember serve and ember test --server lost their fresh-map-per-request behavior, and a project-defined coverageEnvVar was ignored in favour of a hardcoded COVERAGE.
  • In-repo addon coverage was missing its addon/ and addon-test-support/ trees.
  • This addon's own source leaked into every fixture's coverage report, and the HTML reporter wrote files outside its output directory when those paths crossed workspace boundaries.

Vite

Incorporates the intent of #434, with fixes beyond it:

  • Instrumentation moved into a post-enforce Vite transform, so .gjs/.gts are seen as plain JS regardless of plugin ordering.
  • Build-time zero baseline, so files no test imports still report at 0% rather than vanishing.
  • viteTestemMiddleware for ember test --path dist, where the Vite dev server isn't running.

Template coverage

Related to #433 but a different implementation, because that PR's central assumption no longer holds: it relies on camelCase helper names resolving through the classic resolver, and current ember-resolver removed that fallback deliberately — the runtime warning says so outright. Loose mode needs dash-case, strict mode needs camelCase, so they can't share one set.

  • .hbs (loose mode) works with no setup beyond templateCoverage: true in config/coverage.js.
  • .gjs/.gts (strict mode, including apps on ember-strict-application-resolver) works on Vite, via createTemplateCoveragePlugin({ strict: true }) plus templateCoverageImportPlugin, which supplies the JS bindings strict mode requires. On classic/Embroider those files get no template coverage — see below.
  • Covers {{#if}}/{{#unless}}, {{else if}} chains, {{#each}}/{{#each-in}} with {{else}}, and inline conditionals including attribute position.
  • Survives throwUnlessParallelizable, which feature: template coverage #433 doesn't address.

Also fixes adjustCoverage overwriting rather than merging when two raw keys resolve to the same file — the template and JS halves of a .gjs file depend on that merge, and it was already wrong for any other collision.

Tooling

  • Dependencies current across root, addon, and every fixture. TypeScript held at 6.0.3 (typescript-eslint caps below 6.1) and Babel at 7.x (the Ember toolchain isn't Babel 8 ready).
  • One pnpm lint at the root chains formatting plus every workspace package's own lint, fixtures included (pnpm -r --if-present run lint), in about 6 seconds. Five fixtures had lint failures blocking this — missing type="button", a prettier mismatch, a verbatimModuleSyntax type-only import, and a node/no-missing-require false positive where eslint-plugin-node predates exports maps and can't resolve ember-cli-code-coverage/testem even though node resolves it fine. All fixed.
  • CI: action versions bumped, least-privilege permissions, job timeouts, workflow_dispatch, Node 26 added to the matrix.
  • Integration tests serialized — run in parallel, real Ember builds starved each other for CPU and coverage collection degraded under load, making snapshots flap.
  • Consolidated the two diverged CHANGELOG.md files. The nested one uniquely held v2.0.0-beta.1, v2.0.0-beta.2 and v1.0.0-beta.9 (root skipped straight from beta.8 to v1.0.0); those are merged into root at their chronological positions and the nested file removed. Nothing was dropped from root — every version in the nested file is verified present.

Testing

58 tests across 18 files. Restores the in-repo-addon, custom-path, in-repo-engine, v1-addon and scoped-v1-addon fixtures deleted during modernization (they cover namespace mapping and modifyAssetLocation, the most breakage-prone code here), and adds fixtures for Vite and for template coverage.

Not verified / open

  • Strict-mode template coverage does not work on classic/Embroider, and this is now tested rather than assumed. ember-template-imports converts .gjs through its own preprocessor, and the AST plugin registered via setupPreprocessorRegistry never reaches the result; there's no transforms hook on that path to register against, which is why the Vite setup needs hand-wiring in babel.config.mjs and a classic app has no equivalent file for it. Those templates are silently skipped rather than breaking the build — confirmed by compiling a real {{#if}} in a .gjs file. Their JavaScript is still covered.

@aklkv
aklkv marked this pull request as ready for review September 11, 2026 08:59
Continues the modernization branch: fixes classic and Embroider
regressions introduced by the rewrite, finishes Vite support, adds
Glimmer template branch coverage, and brings dependencies current.

Classic/Embroider fixes:
- Restore serverMiddleware, --server handling, and custom
  coverageEnvVar support in addon-main.cjs, lost in the rewrite
- Fix Embroider coverage silently dropping every app file: the
  default exclude switched from a shallow to a depth-independent
  glob, which now also matches Embroider's rewritten-app tree;
  added a negated re-include for it
- Fix this addon's own linked source leaking into every fixture's
  coverage report, which could escape a report's output directory
  entirely when relative paths crossed workspace boundaries;
  excluded via the installed package root instead of a name-based
  glob, since the latter also matches this repo's own checkout name
- Restore the in-repo-addon, custom-path, in-repo-engine, v1-addon,
  and scoped-v1-addon fixtures deleted during modernization, bumped
  to ember-auto-import 2.x (v2 addons require it)

Vite support:
- Move instrumentation into a post-enforce Vite transform instead of
  documenting it as a @rollup/plugin-babel concern, so .gjs/.gts see
  plain JS regardless of plugin ordering
- Add a build-time coverage baseline so files no test imports still
  report at 0% instead of silently vanishing from Vite builds
- Add viteTestemMiddleware for `ember test --path dist`, where the
  Vite dev server (and its own /write-coverage handler) isn't running

Template coverage:
- New Glimmer AST plugin instrumenting {{#if}}/{{#unless}},
  {{else if}} chains, {{#each}}/{{#each-in}} with {{else}}, and
  inline conditionals, reporting branches through the existing
  Istanbul pipeline
- Browser runtime and app-tree re-exports for the coverage helpers;
  a parallel-babel-safe wrapper so template coverage doesn't disable
  Babel's `throwUnlessParallelizable`
- Scoped to .hbs; .gjs/.gts strict-mode templates are not yet covered

Also:
- Bump dependencies across root, the addon, and every fixture to
  current safe versions; hold TypeScript at 6.0.3 (typescript-eslint
  ceiling) and Babel at 7.x (Ember toolchain isn't Babel 8 ready)
- Serialize integration tests (fileParallelism: false): running them
  in parallel let real Ember builds starve each other for CPU,
  degrading coverage collection under load and making snapshots flap
- Consolidate lint into one `pnpm lint` at the root, chaining the
  addon's own js/types/publish checks via `pnpm -r --filter
  '!./test-packages/**'`, plus root formatting
- Modernize CI: bump action versions, add permissions/timeouts/
  workflow_dispatch, add Node 26 to the test matrix
- Drop the root vite devDependency (only vitest/config was needed),
  the stale root .prettierrc.js and .tool-versions, and the
  untracked tsconfig.tsbuildinfo build cache
Closes the three gaps between this branch and what issue ember-cli-code-coverage#431 and PRs
ember-cli-code-coverage#433/ember-cli-code-coverage#434 set out to do.

Strict-mode template coverage (.gjs/.gts):
- createTemplateCoveragePlugin now takes `strict: true`, emitting
  camelCase helper references (coverageMark) instead of dash-cased
  ones. The two can't be shared: loose mode resolves helpers by name
  through the classic resolver, which current ember-resolver versions
  require to be dash-cased — they removed the camelCase fallback as a
  deliberate bug fix. Strict mode has no resolver at all, so every
  reference must be a JS binding, and a dash isn't a valid identifier
  character.
- New templateCoverageImportPlugin supplies those bindings, injecting
  the three helper imports from a Babel pre() hook so they exist before
  babel-plugin-ember-template-compilation validates template scope.
- Skip templates outside the project and anything under node_modules:
  Embroider rewrites classic v1 addon dependencies into v2-compatible
  template() calls through the host app's own `transforms`, and those
  never went through the import plugin, so instrumenting them broke the
  build outright.
- Derive the coverage key from `env.filename` when the preprocessor
  registry didn't set `env.meta.moduleName` (it never does on the
  strict-mode path), so entries no longer collapse onto a shared
  placeholder key.
- Suffix that key so it doesn't resolve to a real file. A .gjs file's
  own JS coverage has a real source map, and applying it to template
  positions read off the pre-compilation AST produced invalid columns
  and crashed reporting for the whole payload.

Fix adjustCoverage discarding coverage on key collision:
Two raw keys resolving to the same file overwrote rather than merged,
so one silently won. Merges via istanbul-lib-coverage now — the
template and JS halves of a .gjs file depend on it, and it was already
wrong for any other collision.

Standalone CLI:
`coverage-merge` bin runs the merge without ember-cli, for projects
with no ember-cli command layer. Takes optional --root and --config.

New /core subpath export, exposing the config, path-mapping and report
utilities without pulling in babel, vite, or testem code.
The template coverage AST plugin skipped files outside the project root,
which silently excluded every template on classic and Embroider builds:
those compile out of broccoli temp directories, so an app's own templates
routinely have paths nowhere near the project. Loose mode escaped it only
because `env.filename` isn't set on that path.

Deciding this on `node_modules` alone is both sufficient and correct. The
dependencies that motivated the filter — a v1 addon Embroider rewrites
through the host app's own `transforms` — are under `node_modules`, so
containment never did the work; it only broke the legitimate case. The
`root` option is dropped rather than kept as a footgun.

Also documents, with a test rather than an assumption, that strict-mode
`.gjs`/`.gts` templates get no coverage on classic/Embroider at all:
ember-template-imports converts them through its own preprocessor and the
AST plugin registered via setupPreprocessorRegistry never reaches the
result, with no `transforms` hook on that path to register against. They
are skipped silently rather than breaking the build; their JavaScript is
still covered. The README said this combination would work with the same
babel config a Vite app uses, which isn't true — a classic app has no
equivalent file to put it in.

Consolidates the two diverged CHANGELOG.md files. The nested one uniquely
held v2.0.0-beta.1, v2.0.0-beta.2 and v1.0.0-beta.9, which root skipped
between beta.8 and v1.0.0; those are merged into root at their
chronological positions and the nested file removed. Every version in the
nested file is verified present in root, and nothing was dropped from it.
@aklkv aklkv changed the title feat: modernize feat: Vite support, template coverage, and ember-cli decoupling Sep 11, 2026
The README told Embroider users to pass `buildBabelPlugin({ embroider:
true })`, but that option no longer exists — it was how earlier versions
repointed Istanbul's cwd at the rewritten app, which the re-include
pattern now handles automatically. Passing it did nothing.

`my-embroider-app` already builds without it and reports coverage
correctly, which is what proves the flag is unnecessary; the
template-imports fixture was still carrying it as dead config.
The root lint task skipped everything under test-packages, so the fixture
apps' own lint scripts had never run in this workspace. Turning them on
surfaced five real failures, fixed here:

- add the missing `type="button"` that ember-template-lint requires in the
  gjs/gts fixtures
- reformat the vite app's components and tests per its own prettier config
- import `SetupTestOptions` as a type, which verbatim module syntax requires
- silence `node/no-missing-require` in the two classic fixtures, since
  eslint-plugin-node is unmaintained and cannot resolve package `exports`
  subpaths such as `ember-cli-code-coverage/testem`

Also ignore `.eslintcache` unanchored so it covers every workspace package
rather than only the root.
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