ember.nvp 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/.github/workflows/ci.yml +22 -0
  2. package/.release-plan.json +35 -7
  3. package/CHANGELOG.md +32 -0
  4. package/README.md +41 -28
  5. package/RELEASE.md +16 -14
  6. package/package.json +1 -1
  7. package/packages/build-tooling-utils/README.md +7 -12
  8. package/packages/build-tooling-utils/package.json +2 -2
  9. package/packages/build-tooling-utils/src/extensions.ts +4 -3
  10. package/packages/build-tooling-utils/src/maybe-babel.ts +18 -15
  11. package/packages/rolldown/README.md +105 -100
  12. package/packages/rolldown/index.ts +37 -24
  13. package/packages/rolldown/package.json +4 -4
  14. package/packages/rolldown/src/app-reexports.test.ts +4 -3
  15. package/packages/rolldown/src/app-reexports.ts +37 -27
  16. package/packages/rolldown/src/babel.test.ts +10 -5
  17. package/packages/rolldown/src/babel.ts +88 -69
  18. package/packages/rolldown/src/config.ts +21 -17
  19. package/packages/rolldown/src/dts.test.ts +42 -26
  20. package/packages/rolldown/src/externals.test.ts +16 -11
  21. package/packages/rolldown/src/externals.ts +18 -12
  22. package/packages/rolldown/src/isolated-declarations.ts +56 -43
  23. package/packages/rolldown/src/plugin-like.ts +18 -15
  24. package/packages/rolldown/src/published-types.test.ts +18 -13
  25. package/packages/rolldown/src/transform.test.ts +26 -17
  26. package/packages/rolldown/src/transform.ts +68 -48
  27. package/packages/vite/README.md +10 -11
  28. package/packages/vite/package.json +2 -2
  29. package/packages/vite/src/ember.ts +44 -36
  30. package/packages/vite/src/test-html.ts +13 -11
  31. package/src/bases/README.md +6 -3
  32. package/src/bases/minimal-app/app.test.ts +7 -4
  33. package/src/bases/minimal-app/files/app/app.ts +2 -3
  34. package/src/bases/minimal-app/files/app/router.ts +3 -3
  35. package/src/bases/minimal-app/index.js +10 -6
  36. package/src/bases/minimal-custom-element/files/README.md +16 -12
  37. package/src/bases/minimal-extension/files/README.md +13 -5
  38. package/src/bases/minimal-extension/files/app/app.ts +2 -3
  39. package/src/bases/minimal-extension/files/app/config.ts +5 -3
  40. package/src/bases/minimal-extension/index.js +16 -11
  41. package/src/bases/minimal-library/files/README.md +8 -7
  42. package/src/bases/minimal-library/index.js +21 -15
  43. package/src/cli/index.js +23 -18
  44. package/src/cli/questions/write.js +12 -10
  45. package/src/layers/are-the-types-wrong/index.js +4 -4
  46. package/src/layers/github-actions/index.js +7 -4
  47. package/src/layers/inspector-support/index.js +2 -2
  48. package/src/layers/qunit/index.js +18 -14
  49. package/src/layers/readme/index.js +2 -4
  50. package/src/layers/renovate/index.js +2 -1
  51. package/src/layers/typescript/index.js +12 -12
  52. package/src/layers/vitest/index.js +7 -3
  53. package/src/utils/babel.js +3 -2
  54. package/src/utils/buffer.js +2 -2
  55. package/src/utils/cli.js +4 -4
  56. package/src/utils/fs.js +23 -14
  57. package/src/utils/inspector-support.js +22 -19
  58. package/src/utils/npm.js +15 -12
  59. package/src/utils/project-type.js +19 -0
  60. package/src/utils/project.js +4 -13
  61. package/src/utils/remove-types.js +2 -4
  62. package/src/utils/rewrite-imports.js +43 -31
  63. package/src/utils/stage.js +44 -33
  64. package/src/utils/tsdown-config.js +6 -5
  65. package/src/utils/types.ts +9 -8
  66. package/test/ember.nvp/base-library.test.ts +6 -4
  67. package/test/ember.nvp/generator-replace-or-update.test.ts +6 -4
  68. package/test/ember.nvp/inspector-support.test.ts +4 -3
  69. package/test/ember.nvp/layer-inspector-support.test.ts +2 -3
  70. package/test/ember.nvp/layer-publish-checks.test.ts +2 -3
  71. package/test/ember.nvp/layer-qunit.test.ts +2 -2
  72. package/test/ember.nvp/layer-readme.test.ts +2 -2
  73. package/test/ember.nvp/layer-vitest.test.ts +16 -9
  74. package/test/ember.nvp/layers-load.test.ts +26 -0
  75. package/test/ember.nvp/library-src-fixtures.ts +7 -3
  76. package/test/ember.nvp/permutations/shared.ts +19 -12
  77. package/test/ember.nvp/publish-configs.test.ts +44 -31
  78. package/test/ember.nvp/rewrite-imports.test.ts +4 -3
  79. package/test/ember.nvp/scoped-css-library.test.ts +6 -4
  80. package/test/test-helpers/permutate.test.ts +3 -3
  81. package/test/test-helpers.ts +10 -6
@@ -1,11 +1,11 @@
1
1
  # `@nullvoxpopuli/ember-rolldown`
2
2
 
3
3
  A batteries-included meta-plugin for building Ember v2 libraries (addons) with
4
- [rolldown](https://rolldown.rs/) — or [tsdown](https://tsdown.dev/), which is
5
- built on top of rolldown and also emits `.d.ts` files.
4
+ [rolldown](https://rolldown.rs/), or with [tsdown](https://tsdown.dev/), which
5
+ is built on top of rolldown and also emits `.d.ts` files.
6
6
 
7
- It compiles `.gts`/`.gjs` and template-tag (`<template>`) source into
8
- publishable output, so a single `ember()` call replaces the usual stack of
7
+ It compiles `.gts` / `.gjs` and template-tag (`<template>`) source into
8
+ publishable output. A single `ember()` call replaces the usual stack of
9
9
  `@embroider/*` externals handling, content-tag preprocessing, and babel wiring.
10
10
 
11
11
  ## Install
@@ -14,17 +14,18 @@ publishable output, so a single `ember()` call replaces the usual stack of
14
14
  npm add --save-dev @nullvoxpopuli/ember-rolldown
15
15
  ```
16
16
 
17
- Requires node 24+, and — since these packages ship TypeScript source — a
18
- modern TypeScript when type-checking: 6+ with `lib` covering `es2025` (e.g.
19
- `esnext`).
17
+ Requires node 24+.
18
+
19
+ These packages ship TypeScript source, so type-checking needs a modern
20
+ TypeScript: 6+ with `lib` covering `es2025` (e.g. `esnext`).
20
21
 
21
22
  ## Usage
22
23
 
23
- Import `defineConfig` from the bundler you're using — `tsdown` or `rolldown` —
24
- so it carries that tool's own config types; `@nullvoxpopuli/ember-rolldown`
25
- provides `ember()` (the defaults travel with the plugin, not `defineConfig`).
24
+ Import `defineConfig` from the bundler you use, `tsdown` or `rolldown`, so it
25
+ carries that tool's own config types. `@nullvoxpopuli/ember-rolldown` provides
26
+ `ember()`. The defaults travel with the plugin, not with `defineConfig`.
26
27
 
27
- In your `tsdown.config.js` (recommended — emits declarations):
28
+ In your `tsdown.config.js` (recommended, because it emits declarations):
28
29
 
29
30
  ```js
30
31
  import { defineConfig } from "tsdown";
@@ -36,10 +37,10 @@ export default defineConfig({
36
37
  });
37
38
  ```
38
39
 
39
- This builds your entry to `dist/*.js` and `dist/*.d.ts` with sourcemaps,
40
- cleaning `dist/` between builds and leaving the ember virtual packages to the
41
- consuming app. You choose the `entry` and `plugins`; any tsdown option you
42
- pass wins.
40
+ This builds your entry to `dist/*.js` and `dist/*.d.ts` with sourcemaps. It
41
+ cleans `dist/` between builds and leaves the ember virtual packages to the
42
+ consuming app. You choose the `entry` and `plugins`. Any tsdown option you pass
43
+ wins.
43
44
 
44
45
  Or in a plain `rolldown.config.js`:
45
46
 
@@ -53,16 +54,16 @@ export default defineConfig({
53
54
  });
54
55
  ```
55
56
 
56
- A babel config is optional. Without one, `ember()` compiles templates
57
- (to `precompileTemplate`), decorators (via
57
+ A babel config is optional. Without one, `ember()` compiles templates (to
58
+ `precompileTemplate`), decorators (via
58
59
  [decorator-transforms](https://github.com/ef4/decorator-transforms)), and
59
- TypeScript; with one, your config runs instead. A `babel.publish.config.*` is
60
- preferred over a `babel.config.*` — see
60
+ TypeScript. With one, your config runs instead. A `babel.publish.config.*` is
61
+ preferred over a `babel.config.*`. See
61
62
  [Publish vs. development babel config](#publish-vs-development-babel-config).
62
63
 
63
64
  ### Entrypoints
64
65
 
65
- Entries may be any extension — `.ts`, `.js`, `.gts`, `.gjs`:
66
+ Entries may be any extension: `.ts`, `.js`, `.gts`, `.gjs`.
66
67
 
67
68
  ```js
68
69
  export default defineConfig({
@@ -72,19 +73,20 @@ export default defineConfig({
72
73
  ```
73
74
 
74
75
  Whatever the source extension, the emitted `.js` and `.d.ts` paths mirror the
75
- entry paths (that's how tsdown's dts support works): the example emits
76
- `dist/index.js` + `dist/index.d.ts` and `dist/components/menu.js` +
77
- `dist/components/menu.d.ts`. Type imports resolve against those emitted
78
- paths, so your entries — together with your `exports` map — _are_ your
79
- public API: a module that isn't an entry has no stable `dist/` path of its
80
- own and is only reachable through the entrypoints that re-export it.
76
+ entry paths. The example emits `dist/index.js` + `dist/index.d.ts` and
77
+ `dist/components/menu.js` + `dist/components/menu.d.ts`.
78
+
79
+ Type imports resolve against those emitted paths. So your entries, together
80
+ with your `exports` map, _are_ your public API. A module that isn't an entry has
81
+ no stable `dist/` path of its own. It is only reachable through the entrypoints
82
+ that re-export it.
81
83
 
82
84
  ### Declarations
83
85
 
84
- Declarations are emitted with isolated declarations — the only declaration
85
- pipeline that can see `<template>` (`.gts`/`.gjs`) modules, which exist only
86
- inside the bundler's module graph. The tsconfig your build uses must enable it
87
- (`ember()` errors otherwise):
86
+ Declarations are emitted with isolated declarations. That is the only
87
+ declaration pipeline that can see `<template>` (`.gts` / `.gjs`) modules, which
88
+ exist only inside the bundler's module graph. The tsconfig your build uses must
89
+ enable it. `ember()` errors otherwise.
88
90
 
89
91
  ```jsonc
90
92
  {
@@ -106,14 +108,16 @@ export const Badge: TOC<BadgeSignature> = <template>...</template>;
106
108
  #### Keeping the constraint off dev-only code
107
109
 
108
110
  `isolatedDeclarations` is a constraint on how _published_ code is written, so it
109
- should cover only the code you emit declarations for. If your package also holds
110
- dev-only code — a demo app, in-package tests — putting the flag on the single
111
- `tsconfig.json` that covers everything would force explicit annotations on demo
112
- components and test helpers that never get a `.d.ts` emitted for them.
111
+ must cover only the code you emit declarations for.
112
+
113
+ If your package also holds dev-only code (a demo app, in-package tests), putting
114
+ the flag on the single `tsconfig.json` that covers everything would force
115
+ explicit annotations on demo components and test helpers that never get a
116
+ `.d.ts`.
113
117
 
114
118
  The tsconfig `ember()` checks is the one
115
- [tsdown's `tsconfig` option](https://tsdown.dev/options/tsconfig) points at, so
116
- point the build at a publish-only config and leave `tsconfig.json` alone for
119
+ [tsdown's `tsconfig` option](https://tsdown.dev/options/tsconfig) points at. So
120
+ point the build at a publish-only config, and leave `tsconfig.json` alone for
117
121
  editors and `tsc --noEmit`:
118
122
 
119
123
  ```js
@@ -127,21 +131,21 @@ export default defineConfig({
127
131
  ```
128
132
 
129
133
  Any path works, so the publish config can live wherever you keep build
130
- configuration — `tsconfig: "./config/tsconfig.publish.json"`. (A directory works
131
- too: `tsconfig: "./config"` picks up `config/tsconfig.json`.)
134
+ configuration: `tsconfig: "./config/tsconfig.publish.json"`. A directory works
135
+ too: `tsconfig: "./config"` picks up `config/tsconfig.json`.
132
136
 
133
- `tsconfig: false` is rejected while declarations are on: with no tsconfig there
137
+ `tsconfig: false` is rejected while declarations are on. With no tsconfig there
134
138
  is no `isolatedDeclarations`, and the fallback pipeline can't see compiled
135
139
  `.gts`, so it would fail later with "Source file not found". Set `dts: false`
136
140
  alongside it if the library ships no types.
137
141
 
138
- A tsconfig's relative paths resolve against the file itself, so a config kept in
142
+ A tsconfig's relative paths resolve against the file itself. So a config kept in
139
143
  `config/` wants `"include": ["../src/**/*"]` and `"rootDir": "../src"`. The build
140
- won't tell you if you get that wrong — tsdown drives declaration emit from
141
- `entry`, not from the tsconfig's `include` — but `tsc`/`ember-tsc` and your
142
- editor will, if you ever point them at that config.
144
+ won't tell you if you get that wrong, because tsdown drives declaration emit
145
+ from `entry`, not from the tsconfig's `include`. `tsc` / `ember-tsc` and your
146
+ editor will tell you, if you ever point them at that config.
143
147
 
144
- The tradeoff: isolated-declaration errors in `src` then surface when you build
148
+ The tradeoff: isolated-declaration errors in `src` then surface when you build,
145
149
  rather than in your editor, since the editor uses `tsconfig.json`. Run the build
146
150
  (or `tsc --noEmit -p tsconfig.publish.json`) in CI so nothing lands unchecked.
147
151
 
@@ -149,24 +153,24 @@ rather than in your editor, since the editor uses `tsconfig.json`. Run the build
149
153
 
150
154
  Components that import co-located CSS (`import './popup.css'`) need
151
155
  [`@tsdown/css`](https://www.npmjs.com/package/@tsdown/css) installed in your
152
- library — tsdown auto-detects it and bundles every imported stylesheet into a
153
- single CSS file in `dist/`. Install the version matching your `tsdown`
154
- version (they're released in lockstep):
156
+ library. tsdown auto-detects it and bundles every imported stylesheet into a
157
+ single CSS file in `dist/`. Install the version matching your `tsdown` version.
158
+ They are released in lockstep.
155
159
 
156
160
  ```bash
157
161
  npm add --save-dev @tsdown/css
158
162
  ```
159
163
 
160
- Without it, tsdown's css-guard fails the build on the first CSS import — and
161
- because the importing component module never loads, any declaration that
162
- references that component dangles, surfacing as misleading
163
- `UNLOADABLE_DEPENDENCY` errors on `<component>.d.ts` files.
164
+ Without it, tsdown's css-guard fails the build on the first CSS import. Because
165
+ the importing component module never loads, any declaration that references
166
+ that component dangles. That surfaces as misleading `UNLOADABLE_DEPENDENCY`
167
+ errors on `<component>.d.ts` files.
164
168
 
165
169
  ### ember-scoped-css
166
170
 
167
171
  [ember-scoped-css](https://github.com/auditboard/ember-scoped-css) works with
168
- this pipeline: its template transform rides along via `ember()`'s
169
- `babel.templateTransforms` option, and its unplugin (`ember-scoped-css/rollup`)
172
+ this pipeline. Its template transform rides along via `ember()`'s
173
+ `babel.templateTransforms` option. Its unplugin (`ember-scoped-css/rollup`)
170
174
  resolves the scoped CSS requests the transform injects:
171
175
 
172
176
  ```js
@@ -191,33 +195,32 @@ export default defineConfig({
191
195
  ```
192
196
 
193
197
  This scopes co-located `.css` files, inline `<style scoped>` blocks, and the
194
- `scopedClass` pseudo-helper (that last one is what the `babel.plugins` entry
195
- handles — leave it off if you don't use `scopedClass` in module code).
198
+ `scopedClass` pseudo-helper. That last one is what the `babel.plugins` entry
199
+ handles. Leave it off if you don't use `scopedClass` in module code.
196
200
 
197
- `css.inject` matters for libraries: it keeps the `import "./style.css"`
198
- statement in `dist/index.js`, so consuming apps pull the styles in through
199
- the module graph — without it the bundled CSS is emitted but nothing loads
200
- it.
201
+ `css.inject` matters for libraries. It keeps the `import "./style.css"`
202
+ statement in `dist/index.js`, so consuming apps pull the styles in through the
203
+ module graph. Without it, the bundled CSS is emitted but nothing loads it.
201
204
 
202
205
  ## What `ember()` does
203
206
 
204
207
  `ember()` returns an array of rolldown plugins:
205
208
 
206
- - **`emberIsolatedDeclarations()`** — errors when the tsconfig the build uses
209
+ - **`emberIsolatedDeclarations()`** errors when the tsconfig the build uses
207
210
  (tsdown's `tsconfig` option, defaulting to `tsconfig.json`) is present without
208
211
  `isolatedDeclarations: true`.
209
- - **`emberExternals()`** — keeps your `dependencies`, `peerDependencies`, and
210
- the ember virtual packages (e.g. `@ember/component`, `@glimmer/tracking`, the
212
+ - **`emberExternals()`** keeps your `dependencies`, `peerDependencies`, and the
213
+ ember virtual packages (e.g. `@ember/component`, `@glimmer/tracking`, the
211
214
  template compiler) external, so the consuming app resolves them.
212
- - **`emberBundle()`** — bundle mode only, in place of `emberExternals()`: makes
213
- the ember virtual packages resolvable, so they can be bundled.
214
- - **`emberTransform()`** — preprocesses `<template>` via
215
+ - **`emberBundle()`** replaces `emberExternals()` in bundle mode. It makes the
216
+ ember virtual packages resolvable, so they can be bundled.
217
+ - **`emberTransform()`** preprocesses `<template>` via
215
218
  [content-tag](https://github.com/embroider-build/content-tag) and maps
216
- `.gts`/`.gjs` to `.ts`/`.js` so rolldown understands them. Also rewrites
217
- `.gts` specifiers in emitted `.d.ts` files.
218
- - **`emberBabel()`** — runs babel with `babelHelpers: "bundled"`, but only on
219
- the files that actually need it (template-tag, decorators, template imports);
220
- everything else stays on rolldown's fast native (oxc) transform. Uses your
219
+ `.gts` / `.gjs` to `.ts` / `.js` so rolldown understands them. It also
220
+ rewrites `.gts` specifiers in emitted `.d.ts` files.
221
+ - **`emberBabel()`** runs babel with `babelHelpers: "bundled"`, but only on the
222
+ files that need it (template-tag, decorators, template imports). Everything
223
+ else stays on rolldown's fast native (oxc) transform. It uses your
221
224
  `babel.publish.config.*` (root or `config/`) in preference to your
222
225
  `babel.config.*`.
223
226
 
@@ -236,11 +239,12 @@ ember({
236
239
  });
237
240
  ```
238
241
 
239
- Each option is documented on `BabelOptions`. `templateTransforms` feeds
240
- template AST transforms to the default template-compilation step; it can't be
241
- combined with a babel config file — a config lists
242
- `babel-plugin-ember-template-compilation` itself, so its transforms belong
243
- there.
242
+ Each option is documented on `BabelOptions`.
243
+
244
+ `templateTransforms` feeds template AST transforms to the default
245
+ template-compilation step. It can't be combined with a babel config file. A
246
+ config lists `babel-plugin-ember-template-compilation` itself, so its
247
+ transforms belong there.
244
248
 
245
249
  ### Bundle mode
246
250
 
@@ -276,12 +280,13 @@ Do not combine bundle mode with tsdown's `unbundle` option.
276
280
 
277
281
  ### Publish vs. development babel config
278
282
 
279
- A library's plain `babel.config.*` is usually its _development_ config: it
283
+ A library's plain `babel.config.*` is usually its _development_ config. It
280
284
  compiles `@embroider/macros` away, targets the wire format, and wires up
281
- whatever the in-package demo app or test suite needs. None of that belongs in a
282
- published artifact — macros must survive for the consuming app to evaluate, and
283
- the wire format is private between one template compiler and one glimmer
284
- runtime of the same version.
285
+ whatever the in-package demo app or test suite needs.
286
+
287
+ None of that belongs in a published artifact. Macros must survive for the
288
+ consuming app to evaluate. The wire format is private between one template
289
+ compiler and one glimmer runtime of the same version.
285
290
 
286
291
  Babel's own resolution can't tell those apart, so `ember()` looks for a config
287
292
  named for publishing first. With no explicit `configFile`, detection is:
@@ -289,8 +294,8 @@ named for publishing first. With no explicit `configFile`, detection is:
289
294
  1. `babel.publish.config.{mjs,cjs,js,mts,cts,ts,json}` in the package root
290
295
  2. the same names in `config/`
291
296
  3. otherwise babel's own resolution (`babel.config.*`, honoring `rootMode`)
292
- 4. otherwise no config file — templates, decorators and TypeScript are still
293
- handled by the built-in defaults
297
+ 4. otherwise no config file. Templates, decorators and TypeScript are still
298
+ handled by the built-in defaults.
294
299
 
295
300
  So a library that keeps both configs needs no `babel` option at all:
296
301
 
@@ -307,18 +312,18 @@ my-addon/
307
312
  babel.config.mjs
308
313
  config/
309
314
  babel.publish.config.mjs # what ember() uses
310
- tsconfig.publish.json # see below
315
+ tsconfig.publish.json # see above
311
316
  ```
312
317
 
313
- Set `configFile` explicitly to override that (`configFile: false` ignores config
314
- files entirely, which is what you want when your publish config would just
315
- restate the built-in defaults).
318
+ Set `configFile` explicitly to override that. `configFile: false` ignores
319
+ config files entirely. That is what you want when your publish config would
320
+ only restate the built-in defaults.
316
321
 
317
322
  ## App re-exports
318
323
 
319
- For libraries whose modules must appear in the consuming app's namespace
320
- (classic resolution: `{{a-component}}`, services and helpers looked up by
321
- name), `appReexports()` does the same job as `@embroider/addon-dev`'s
324
+ Some libraries need their modules to appear in the consuming app's namespace
325
+ for classic resolution: `{{a-component}}`, services and helpers looked up by
326
+ name. For those, `appReexports()` does the same job as `@embroider/addon-dev`'s
322
327
  `appReexports` rollup plugin. It's a separate import because most libraries
323
328
  don't need it:
324
329
 
@@ -333,10 +338,12 @@ export default defineConfig({
333
338
  });
334
339
  ```
335
340
 
336
- With no arguments, top-level services (`services/*`) are re-exported — under
341
+ With no arguments, top-level services (`services/*`) are re-exported. Under
337
342
  strict mode, components and helpers are imported, but services are still
338
- injected by name. A string or array of strings is the include glob(s),
339
- optionally followed by the remaining options; an object gives full control:
343
+ injected by name.
344
+
345
+ A string or array of strings is the include glob(s), optionally followed by the
346
+ remaining options. An object gives full control:
340
347
 
341
348
  ```js
342
349
  appReexports(); // services/*
@@ -346,16 +353,14 @@ appReexports("components/**", { exclude: ["components/-private/**"] });
346
353
  appReexports({ include: ["services/*", "helpers/*"], exclude: [...] });
347
354
  ```
348
355
 
349
- For every built file matching `include` (minus `exclude` and `.d.ts` files),
350
- it writes a module under `dist/_app_/` re-exporting from the library's own
351
- name, and records the set in `package.json` under `ember-addon.app-js`.
352
- `mapFilename` renames a re-export; `exports` picks which bindings it forwards
356
+ For every built file matching `include` (minus `exclude` and `.d.ts` files), it
357
+ writes a module under `dist/_app_/` re-exporting from the library's own name,
358
+ and records the set in `package.json` under `ember-addon.app-js`.
359
+ `mapFilename` renames a re-export. `exports` picks which bindings it forwards
353
360
  (default `["default"]`).
354
361
 
355
- Unlike the embroider plugin, nothing is written unless its content actually
356
- differs from what is on disk — `_app_/` modules are compared byte-for-byte
357
- and `package.json` is only rewritten when the `app-js` map itself changed —
358
- so rebuilds don't re-trigger file watchers.
362
+ Nothing is written unless its content differs from what is on disk, so rebuilds
363
+ don't re-trigger file watchers.
359
364
 
360
365
  ## Credit
361
366
 
@@ -27,39 +27,52 @@ interface Config {
27
27
  */
28
28
  bundle?: boolean;
29
29
  /**
30
- * Options for the babel step; see `BabelOptions`.
30
+ * Options for the babel step.
31
+ * See `BabelOptions`.
31
32
  */
32
33
  babel?: BabelOptions;
33
34
  }
34
35
 
35
36
  /**
36
- * A batteries-included plugin for building Ember v2 libraries (addons) with
37
- * rolldown (or tsdown, which is built on rolldown).
37
+ * A batteries-included plugin for building Ember v2 libraries (addons)
38
+ * with rolldown, or with tsdown (which is built on rolldown).
38
39
  *
39
- * It bundles everything needed to compile `.gts`/`.gjs` and template-tag
40
- * (`<template>`) source into publishable output:
40
+ * It bundles everything needed to compile `.gts` / `.gjs` and
41
+ * template-tag (`<template>`) source into publishable output:
41
42
  *
42
- * - `emberConfig()` — applies sensible tsdown defaults for a library build
43
- * (sourcemaps, `clean`, `dts`, `.js`/`.d.ts` extensions, quiet logging);
44
- * anything you set explicitly still wins.
45
- * - `emberIsolatedDeclarations()` — errors when a tsconfig.json is present
46
- * without `isolatedDeclarations: true` (required: it is the only
47
- * declaration pipeline that can see compiled template-tag modules).
48
- * - `emberExternals()` — keeps your dependencies, peerDependencies, and the
49
- * ember virtual packages external, so consuming apps resolve them.
50
- * - `emberBundle()` — bundle mode only, in place of `emberExternals()`:
51
- * aliases the ember virtual packages into ember-source,
43
+ * - `emberConfig()`
44
+ * applies sensible tsdown defaults for a library build
45
+ * (sourcemaps, `clean`, `dts`, `.js` / `.d.ts` extensions, quiet logging).
46
+ * Anything you set explicitly still wins.
47
+ *
48
+ * - `emberIsolatedDeclarations()`
49
+ * errors when a tsconfig.json is present without `isolatedDeclarations: true`.
50
+ * That is the only declaration pipeline that can see compiled template-tag modules.
51
+ *
52
+ * - `emberExternals()`
53
+ * keeps your dependencies, peerDependencies, and the ember virtual packages external,
54
+ * so consuming apps resolve them.
55
+ *
56
+ * - `emberBundle()`
57
+ * bundle mode only, in place of `emberExternals()`.
58
+ * Aliases the ember virtual packages into ember-source,
52
59
  * whose export conditions pick the build to bundle.
53
- * - `emberTransform()` — preprocesses `<template>` via content-tag and maps
54
- * `.gts`/`.gjs` to `.ts`/`.js` so rolldown can understand them.
55
- * - `emberBabel()` — runs babel (template compilation, decorators, type
56
- * stripping) with `babelHelpers: "bundled"`, but only on the files that
57
- * actually need it (via `maybeBabel`); everything else stays on the fast
58
- * native transform. The library's own `babel.config.js` is used when it
59
- * exists; no config file is required.
60
60
  *
61
- * Usage in `tsdown.config.js` (import `defineConfig` from `tsdown` — or from
62
- * `rolldown` for a `rolldown.config.js` — so it carries the correct types):
61
+ * - `emberTransform()`
62
+ * preprocesses `<template>` via content-tag,
63
+ * and maps `.gts` / `.gjs` to `.ts` / `.js` so rolldown can understand them.
64
+ *
65
+ * - `emberBabel()`
66
+ * runs babel (template compilation, decorators, type stripping)
67
+ * with `babelHelpers: "bundled"`.
68
+ * Only the files that need it go through babel (via `maybeBabel`).
69
+ * Everything else stays on the fast native transform.
70
+ * The library's own `babel.config.js` is used when it exists.
71
+ * No config file is required.
72
+ *
73
+ * Usage in `tsdown.config.js`.
74
+ * Import `defineConfig` from `tsdown` (or from `rolldown` for a `rolldown.config.js`)
75
+ * so it carries the correct types:
63
76
  *
64
77
  * ```js
65
78
  * import { defineConfig } from "tsdown";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nullvoxpopuli/ember-rolldown",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
4
4
  "description": "Meta plugin for building ember v2 libraries/addons with rolldown & tsdown.",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -43,13 +43,13 @@
43
43
  },
44
44
  "packageManager": "pnpm@10.28.0",
45
45
  "dependencies": {
46
- "@babel/core": "^7.28.10",
47
- "@babel/plugin-transform-typescript": "^7.28.10",
46
+ "@babel/core": "^7.28.10 || ^8.0.0",
47
+ "@babel/plugin-transform-typescript": "^7.28.10 || ^8.0.0",
48
48
  "@embroider/core": "^4.4.2",
49
49
  "@nullvoxpopuli/ember-build-tooling-utils": "workspace:*",
50
50
  "babel-plugin-ember-template-compilation": "^4.0.0",
51
51
  "content-tag": "^4.2.0",
52
- "decorator-transforms": "^2.3.2"
52
+ "decorator-transforms": "^2.4.0"
53
53
  },
54
54
  "engines": {
55
55
  "node": ">= 24"
@@ -8,9 +8,10 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
8
8
  import { appReexports, type AppReexportsOptions } from "./app-reexports.ts";
9
9
 
10
10
  /**
11
- * The plugin reads and writes package.json relative to the current working
12
- * directory (matching the other plugins in this package), so each test runs
13
- * inside its own temp project.
11
+ * The plugin reads and writes package.json relative to the current working directory
12
+ * (matching the other plugins in this package).
13
+ *
14
+ * So each test runs inside its own temp project.
14
15
  */
15
16
  describe("appReexports", () => {
16
17
  let projectDir: string;
@@ -5,13 +5,14 @@ import type { RolldownPluginLike } from "./plugin-like.ts";
5
5
 
6
6
  export interface AppReexportsOptions {
7
7
  /**
8
- * Globs, matched against each built filename (relative to the output
9
- * directory), selecting which modules get an app re-export.
8
+ * Globs selecting which modules get an app re-export.
9
+ * Matched against each built filename, relative to the output directory.
10
10
  *
11
- * Defaults to `["services/*"]` -- top-level services only. Under strict
12
- * mode, components and helpers are imported, but services are still
13
- * injected by name, so they are the one thing an app must always be able
14
- * to resolve.
11
+ * Defaults to `["services/*"]`: top-level services only.
12
+ *
13
+ * Under strict mode, components and helpers are imported.
14
+ * Services are still injected by name,
15
+ * so they are the one thing an app must always be able to resolve.
15
16
  *
16
17
  * `.d.ts` files never match.
17
18
  */
@@ -23,33 +24,39 @@ export interface AppReexportsOptions {
23
24
  exclude?: string[];
24
25
 
25
26
  /**
26
- * Rename a re-export: receives the built filename, returns the filename the
27
- * app sees. Defaults to the built filename unchanged.
27
+ * Rename a re-export.
28
+ *
29
+ * Receives the built filename, returns the filename the app sees.
30
+ * Defaults to the built filename unchanged.
28
31
  */
29
32
  mapFilename?: (filename: string) => string;
30
33
 
31
34
  /**
32
- * Which bindings a file's re-export module forwards: an array of names
33
- * (default `["default"]`), or a raw export clause string such as `"*"`.
35
+ * Which bindings a file's re-export module forwards:
36
+ * - an array of names (default `["default"]`)
37
+ * - or a raw export clause string such as `"*"`
34
38
  */
35
39
  exports?: (filename: string) => string[] | string | undefined;
36
40
  }
37
41
 
38
42
  /**
39
- * Merges built modules into the consuming app's namespace, so classic
40
- * resolution (`{{a-component}}`, service/helper lookup by name) finds them --
41
- * the same job as `@embroider/addon-dev`'s `appReexports` rollup plugin.
43
+ * Merges built modules into the consuming app's namespace,
44
+ * so classic resolution finds them
45
+ * (`{{a-component}}`, service / helper lookup by name).
46
+ *
47
+ * The same job as `@embroider/addon-dev`'s `appReexports` rollup plugin.
42
48
  *
43
- * For every built file matching `include` (minus `exclude` and `.d.ts`), it
44
- * writes a tiny module under `<outDir>/_app_/` re-exporting from the
45
- * library's own name, and records the full set in `package.json` under
46
- * `ember-addon.app-js`.
49
+ * For every built file matching `include` (minus `exclude` and `.d.ts`):
50
+ * - writes a tiny module under `<outDir>/_app_/`,
51
+ * re-exporting from the library's own name
52
+ * - records the full set in `package.json` under `ember-addon.app-js`
47
53
  *
48
- * Unlike the embroider plugin, nothing is written unless its content actually
49
- * differs from what is on disk: the `_app_/` modules are compared
50
- * byte-for-byte, and `package.json` is only rewritten when the `app-js` map
51
- * itself changed (so an equivalent map in different formatting is left
52
- * alone). This keeps rebuilds from re-triggering file watchers.
54
+ * Nothing is written unless its content differs from what is on disk:
55
+ * - the `_app_/` modules are compared byte-for-byte
56
+ * - `package.json` is only rewritten when the `app-js` map itself changed
57
+ * (an equivalent map in different formatting is left alone)
58
+ *
59
+ * This keeps rebuilds from re-triggering file watchers.
53
60
  *
54
61
  * This is a separate import, because most libraries don't need it:
55
62
  *
@@ -64,9 +71,12 @@ export interface AppReexportsOptions {
64
71
  * });
65
72
  * ```
66
73
  *
67
- * With no arguments, top-level services (`services/*`) are re-exported. A
68
- * string or array of strings is the include glob(s), optionally followed by
69
- * the remaining options; an object gives full control:
74
+ * With no arguments, top-level services (`services/*`) are re-exported.
75
+ *
76
+ * The first argument can be:
77
+ * - a string or array of strings: the include glob(s),
78
+ * optionally followed by the remaining options
79
+ * - an object: full control
70
80
  *
71
81
  * ```js
72
82
  * appReexports(); // services/*
@@ -97,7 +107,7 @@ export function appReexports(
97
107
  const appJs: Record<string, string> = {};
98
108
 
99
109
  // Sorted so the app-js map (and therefore package.json) is stable
100
- // across builds regardless of chunk emission order.
110
+ // across builds, regardless of chunk emission order.
101
111
  for (const builtFilename of Object.keys(bundle).sort()) {
102
112
  if (!isIncluded(builtFilename, include, resolved.exclude)) continue;
103
113
 
@@ -143,7 +153,7 @@ function writeFileIfChanged(path: string, content: string) {
143
153
  try {
144
154
  existing = readFileSync(path, { encoding: "utf8" });
145
155
  } catch {
146
- // Doesn't exist yet (or isn't readable) -- write it.
156
+ // Doesn't exist yet (or isn't readable): write it.
147
157
  }
148
158
 
149
159
  if (existing === content) return;
@@ -15,8 +15,9 @@ afterEach(() => {
15
15
  });
16
16
 
17
17
  /**
18
- * A babel config whose only plugin rewrites the string `"MARKER"` to `name`, so
19
- * the emitted code says which config file babel actually loaded.
18
+ * A babel config whose only plugin rewrites the string `"MARKER"` to `name`.
19
+ *
20
+ * So the emitted code says which config file babel loaded.
20
21
  */
21
22
  function markerConfig(name: string): string {
22
23
  return [
@@ -37,9 +38,13 @@ function markerConfig(name: string): string {
37
38
  }
38
39
 
39
40
  /**
40
- * Builds a `.gts` entry (so babel actually runs -- `maybeBabel` skips files that
41
- * need no transform) in a temp dir that becomes the cwd, the way config
42
- * detection sees it under the tsdown CLI. Returns the emitted code.
41
+ * Builds a `.gts` entry in a temp dir that becomes the cwd,
42
+ * the way config detection sees it under the tsdown CLI.
43
+ *
44
+ * A `.gts` entry so babel runs at all:
45
+ * `maybeBabel` skips files that need no transform.
46
+ *
47
+ * Returns the emitted code.
43
48
  */
44
49
  async function bundleWithBabel(
45
50
  files: Record<string, string>,