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
@@ -28,14 +28,15 @@ export interface BabelOptions {
28
28
  /**
29
29
  * The babel config file to use.
30
30
  *
31
- * - `undefined`: auto-detect. A `babel.publish.config.*` (in the package root
32
- * or `config/`) wins, because this is a publish build; otherwise babel's own
33
- * config resolution applies (see `detectConfigFile`).
31
+ * - `undefined`: auto-detect.
32
+ * A `babel.publish.config.*` (in the package root or `config/`) wins,
33
+ * because this is a publish build.
34
+ * Otherwise babel's own config resolution applies (see `detectConfigFile`).
34
35
  * - a string: use that config file.
35
36
  * - `false`: ignore config files entirely.
36
37
  *
37
- * Without a config file, templates, decorators, and TypeScript are still
38
- * handled (see `defaultPlugins`).
38
+ * Without a config file, templates, decorators, and TypeScript are still handled
39
+ * (see `defaultPlugins`).
39
40
  */
40
41
  configFile?: string | false;
41
42
  /**
@@ -43,17 +44,20 @@ export interface BabelOptions {
43
44
  */
44
45
  plugins?: PluginItem[];
45
46
  /**
46
- * Template AST transforms (e.g. ember-scoped-css's `scopedCSS.template()`)
47
- * passed to the default `babel-plugin-ember-template-compilation` entry.
47
+ * Template AST transforms passed to the default
48
+ * `babel-plugin-ember-template-compilation` entry.
49
+ * e.g. ember-scoped-css's `scopedCSS.template()`
48
50
  *
49
- * Only valid without a babel config file: with your own config, you list
50
- * `babel-plugin-ember-template-compilation` yourself, so its `transforms`
51
- * belong there.
51
+ * Only valid without a babel config file.
52
+ * With your own config, you list `babel-plugin-ember-template-compilation` yourself,
53
+ * so its `transforms` belong there.
52
54
  */
53
55
  templateTransforms?: Transform[];
54
56
  /**
55
- * Opt additional files into babel (see maybeBabel's `filter`). Use this to
56
- * cover addons that ship code needing babel (e.g. `ember-concurrency`).
57
+ * Opt additional files into babel (see maybeBabel's `filter`).
58
+ *
59
+ * Use this to cover addons that ship code needing babel
60
+ * (e.g. `ember-concurrency`).
57
61
  */
58
62
  filter?: {
59
63
  include: {
@@ -63,27 +67,34 @@ export interface BabelOptions {
63
67
  };
64
68
  }
65
69
 
66
- /** Tried in order, so an ESM config wins over a JSON one of the same name. */
70
+ /**
71
+ * Tried in order, so an ESM config wins over a JSON one of the same name.
72
+ */
67
73
  const PUBLISH_CONFIG_EXTENSIONS = ["mjs", "cjs", "js", "mts", "cts", "ts", "json"];
68
74
 
69
75
  /**
70
- * Tried in order: next to package.json first, then `config/` for libraries that
71
- * keep their build configuration out of the package root.
76
+ * Tried in order:
77
+ * - next to package.json first
78
+ * - then `config/`, for libraries that keep their build configuration
79
+ * out of the package root
72
80
  */
73
81
  const PUBLISH_CONFIG_DIRECTORIES = [".", "config"];
74
82
 
75
83
  /**
76
- * A `babel.publish.config.*` (in the package root or `config/`), if there is
77
- * one.
78
- *
79
- * A library's plain `babel.config.*` is its *development* config: it typically
80
- * compiles `@embroider/macros` away, targets the wire format, and wires up
81
- * whatever the in-package demo app or test suite needs. None of that belongs in
82
- * a published artifact -- macros must stay for the consuming app to evaluate,
83
- * and the wire format is private between one template compiler and one glimmer
84
- * runtime. Babel's own resolution can't tell the two apart (it finds whatever
85
- * `babel.config.*` exists), so a publish build has to prefer the config named
86
- * for publishing.
84
+ * A `babel.publish.config.*` (in the package root or `config/`), if there is one.
85
+ *
86
+ * A library's plain `babel.config.*` is its *development* config.
87
+ * It typically:
88
+ * - compiles `@embroider/macros` away
89
+ * - targets the wire format
90
+ * - wires up whatever the in-package demo app or test suite needs
91
+ *
92
+ * None of that belongs in a published artifact:
93
+ * - macros must stay, for the consuming app to evaluate
94
+ * - the wire format is private between one template compiler and one glimmer runtime
95
+ *
96
+ * Babel's own resolution can't tell the two apart (it finds whatever `babel.config.*` exists),
97
+ * so a publish build has to prefer the config named for publishing.
87
98
  */
88
99
  function detectPublishConfigFile(): string | undefined {
89
100
  for (const directory of PUBLISH_CONFIG_DIRECTORIES) {
@@ -98,14 +109,16 @@ function detectPublishConfigFile(): string | undefined {
98
109
  }
99
110
 
100
111
  /**
101
- * The config this publish build should use: a `babel.publish.config.*` when the
102
- * library has one, else the project's root babel config resolved the way babel
103
- * itself does (`babel.config.{js,cjs,mjs,cts,json}`, honoring `rootMode`
104
- * semantics) -- which for a library whose only config *is* its publish config
105
- * is the same file either way.
106
- *
107
- * The sync API evaluates the config file; on node 24+ that works for native
108
- * ESM configs too (require(esm)).
112
+ * The config this publish build must use:
113
+ * - a `babel.publish.config.*`, when the library has one
114
+ * - else the project's root babel config, resolved the way babel itself does
115
+ * (`babel.config.{js,cjs,mjs,cts,json}`, honoring `rootMode` semantics)
116
+ *
117
+ * For a library whose only config *is* its publish config,
118
+ * that is the same file either way.
119
+ *
120
+ * The sync API evaluates the config file.
121
+ * On node 24+ that works for native ESM configs too (require(esm)).
109
122
  */
110
123
  function detectConfigFile(): string | undefined {
111
124
  const publishConfig = detectPublishConfigFile();
@@ -121,20 +134,21 @@ function detectConfigFile(): string | undefined {
121
134
  * Everything a library without its own babel config needs:
122
135
  *
123
136
  * - TypeScript stripping
137
+ *
124
138
  * - template compilation to `precompileTemplate` (`targetFormat: "hbs"`).
125
- * Published libraries must NOT ship wire format: the wire format is
126
- * private between the template compiler and the glimmer runtime of the
127
- * same version, so baking it in ties the published artifact to the
128
- * consuming app's exact ember-source. The consuming app performs final
129
- * compilation.
139
+ * Published libraries must NOT ship wire format.
140
+ * The wire format is private between the template compiler and the glimmer runtime
141
+ * of the same version, so baking it in ties the published artifact
142
+ * to the consuming app's exact ember-source.
143
+ * The consuming app performs final compilation.
130
144
  *
131
145
  * A bundle-mode build ships that exact ember-source,
132
146
  * so it compiles to the wire format with ember-source's own compiler,
133
147
  * and needs no compiler at runtime.
134
148
  *
135
- * - decorator-transforms, with its runtime left as a bare specifier so the
136
- * consuming app resolves it (the library keeps `decorator-transforms` as
137
- * a real dependency).
149
+ * - decorator-transforms, with its runtime left as a bare specifier
150
+ * so the consuming app resolves it.
151
+ * (the library keeps `decorator-transforms` as a real dependency)
138
152
  *
139
153
  * A bundle-mode build resolves it itself.
140
154
  */
@@ -155,8 +169,8 @@ function defaultPlugins(templateTransforms?: Transform[], bundle = false): Plugi
155
169
  [
156
170
  decoratorTransforms,
157
171
  {
158
- // Emit `import ... from "decorator-transforms/runtime-esm"` as a bare
159
- // specifier so it stays external and the consuming app resolves it.
172
+ // Emit `import ... from "decorator-transforms/runtime-esm"` as a bare specifier,
173
+ // so it stays external and the consuming app resolves it.
160
174
  runtime: { import: "decorator-transforms/runtime-esm" },
161
175
  },
162
176
  ],
@@ -164,29 +178,34 @@ function defaultPlugins(templateTransforms?: Transform[], bundle = false): Plugi
164
178
  }
165
179
 
166
180
  /**
167
- * We still need babel (not just oxc/rolldown's native transforms) for two
168
- * reasons:
169
- *
170
- * - template compilation: `babel-plugin-ember-template-compilation` turns the
171
- * content-tag output into `precompileTemplate` calls; oxc has no equivalent.
172
- * - decorators: Ember uses the legacy/stage-1 decorator signature together
173
- * with `decorator-transforms`, which rewrites decorated class fields
174
- * (`@tracked x`) into getter/setters backed by a runtime. oxc's native
175
- * "legacy decorators" emit tsc-style `__decorate` output, which does not
176
- * match those semantics.
177
- *
178
- * But we don't want to send *every* file through babel and give back the speed
179
- * rolldown/oxc buys us -- so we run babel via `maybeBabel`, which filters down
180
- * to only the files that actually need it (template-tag, decorators, template
181
- * imports). Everything else stays on the native transform.
182
- *
183
- * The library's own babel config is used when it exists -- its
184
- * `babel.publish.config.*` (root or `config/`) in preference to its
185
- * `babel.config.*`, since this is a publish build. Without one, `defaultPlugins` covers templates, decorators,
186
- * and TypeScript, so no config file is required.
187
- *
188
- * Libraries default to `babelHelpers: "bundled"` so the emitted output is
189
- * self-contained.
181
+ * We still need babel (not just oxc / rolldown's native transforms) for two reasons:
182
+ *
183
+ * - template compilation.
184
+ * `babel-plugin-ember-template-compilation` turns the content-tag output
185
+ * into `precompileTemplate` calls.
186
+ * oxc has no equivalent.
187
+ *
188
+ * - decorators.
189
+ * Ember uses the legacy / stage-1 decorator signature together with `decorator-transforms`,
190
+ * which rewrites decorated class fields (`@tracked x`) into getter / setters backed by a runtime.
191
+ * oxc's native "legacy decorators" emit tsc-style `__decorate` output,
192
+ * which does not match those semantics.
193
+ *
194
+ * But we don't want to send *every* file through babel
195
+ * and give back the speed rolldown / oxc buys us.
196
+ *
197
+ * So we run babel via `maybeBabel`, which filters down to only the files that need it
198
+ * (template-tag, decorators, template imports).
199
+ * Everything else stays on the native transform.
200
+ *
201
+ * The library's own babel config is used when it exists.
202
+ * Its `babel.publish.config.*` (root or `config/`) wins over its `babel.config.*`,
203
+ * since this is a publish build.
204
+ *
205
+ * Without one, `defaultPlugins` covers templates, decorators, and TypeScript,
206
+ * so no config file is required.
207
+ *
208
+ * Libraries default to `babelHelpers: "bundled"`, so the emitted output is self-contained.
190
209
  */
191
210
  export function emberBabel(options: BabelOptions = {}): Plugin {
192
211
  const configFile = options.configFile === undefined ? detectConfigFile() : options.configFile;
@@ -214,8 +233,8 @@ export function emberBabel(options: BabelOptions = {}): Plugin {
214
233
 
215
234
  return maybeBabel({
216
235
  ...shared,
217
- // No config file (or the caller opted out): don't let babel go looking
218
- // for one -- we provide the whole plugin list inline.
236
+ // No config file (or the caller opted out).
237
+ // Don't let babel go looking for one: we provide the whole plugin list inline.
219
238
  configFile: false,
220
239
  babelrc: false,
221
240
  plugins: defaultPlugins(options.templateTransforms, options.bundle).concat(
@@ -1,29 +1,33 @@
1
1
  import type { TsdownPlugin, UserConfig } from "tsdown";
2
2
 
3
3
  /**
4
- * Sensible defaults for an Ember v2 library, applied through plugin hooks so
5
- * they travel with `ember()` — you get them whether you use this package's
6
- * `defineConfig`, tsdown's, or a plain `tsdown.config.js` / `rolldown.config.js`.
4
+ * Sensible defaults for an Ember v2 library.
5
+ *
6
+ * Applied through plugin hooks, so they travel with `ember()`.
7
+ * You get them whether you use this package's `defineConfig`, tsdown's,
8
+ * or a plain `tsdown.config.js` / `rolldown.config.js`.
7
9
  *
8
10
  * Every value is applied with `??=`, so anything you set explicitly wins.
9
11
  *
10
- * Under tsdown, the full set applies via the `tsdownConfig` hook (analogous to
11
- * Vite's `config` hook):
12
+ * Under tsdown, the full set applies via the `tsdownConfig` hook
13
+ * (analogous to Vite's `config` hook):
12
14
  *
13
- * - `sourcemap` — emit sourcemaps alongside the output.
14
- * - `clean` — wipe `dist/` between builds.
15
- * - `dts` — emit `.d.ts` declarations (via isolated declarations).
16
- * - `outExtensions` — `.js`/`.d.ts` rather than tsdown's default `.mjs`/`.d.mts`,
17
- * since exports maps conventionally point at `.js`/`.d.ts`.
18
- * - `report` — off; the size report is noise for a library build.
19
- * - `deps.neverBundle` — leave node builtins and the ember virtual packages to
20
- * the consuming app (`emberExternals()` handles the rest).
15
+ * - `sourcemap` — emit sourcemaps alongside the output
16
+ * - `clean` — wipe `dist/` between builds
17
+ * - `dts` — emit `.d.ts` declarations (via isolated declarations)
18
+ * - `outExtensions` — `.js` / `.d.ts` rather than tsdown's default `.mjs` / `.d.mts`
19
+ * (exports maps conventionally point at `.js` / `.d.ts`)
20
+ * - `report` — off.
21
+ * The size report is noise for a library build.
22
+ * - `deps.neverBundle` — leave node builtins and the ember virtual packages
23
+ * to the consuming app (`emberExternals()` handles the rest)
21
24
  *
22
- * A plain rolldown build has no notion of `clean`/`dts`/`outExtensions`/`report`
23
- * (those are tsdown-level concepts), so only the options with rolldown
24
- * equivalents are applied there, via rolldown's own `outputOptions` hook:
25
+ * A plain rolldown build has no notion of `clean` / `dts` / `outExtensions` / `report`.
26
+ * Those are tsdown-level concepts.
27
+ * So only the options with rolldown equivalents are applied there,
28
+ * via rolldown's own `outputOptions` hook:
25
29
  *
26
- * - `output.sourcemap` — on (output option).
30
+ * - `output.sourcemap` — on
27
31
  *
28
32
  * Externals are handled by `emberExternals()` (a `resolveId` hook) in both cases.
29
33
  *
@@ -7,9 +7,13 @@ import { afterEach, describe, expect, it } from "vitest";
7
7
  import { ember } from "../index.ts";
8
8
 
9
9
  interface Dist {
10
- /** Sorted dist-relative paths of every emitted file. */
10
+ /**
11
+ * Sorted dist-relative paths of every emitted file.
12
+ */
11
13
  files: string[];
12
- /** dist-relative path -> content, for every emitted .d.ts and .css file. */
14
+ /**
15
+ * dist-relative path -> content, for every emitted .d.ts and .css file.
16
+ */
13
17
  contents: Record<string, string>;
14
18
  }
15
19
 
@@ -22,12 +26,16 @@ afterEach(() => {
22
26
 
23
27
  /**
24
28
  * Drives `ember()` through a real tsdown build (the dts pipeline included).
25
- * `files` is a map of relative path -> source, written into a temp dir that
26
- * becomes the cwd for the build (the externals and isolated-declarations
27
- * plugins read package.json / tsconfig.json from cwd, like the tsdown CLI).
28
29
  *
29
- * Returns the emitted file list plus the full contents of every declaration
30
- * and css file (js output is covered by transform.test.ts).
30
+ * `files` is a map of relative path -> source.
31
+ * It is written into a temp dir that becomes the cwd for the build.
32
+ * (the externals and isolated-declarations plugins read package.json / tsconfig.json
33
+ * from cwd, like the tsdown CLI)
34
+ *
35
+ * Returns:
36
+ * - the emitted file list
37
+ * - the full contents of every declaration and css file
38
+ * (js output is covered by transform.test.ts)
31
39
  */
32
40
  async function buildFixture(files: Record<string, string>, entry: string[]): Promise<Dist> {
33
41
  const dir = await mkdtemp(path.join(tmpdir(), "ember-rolldown-dts-"));
@@ -81,7 +89,9 @@ async function buildFixture(files: Record<string, string>, entry: string[]): Pro
81
89
  return dist;
82
90
  }
83
91
 
84
- /** One printable document of every captured file, for a single snapshot. */
92
+ /**
93
+ * One printable document of every captured file, for a single snapshot.
94
+ */
85
95
  function printed(dist: Dist): string {
86
96
  return Object.entries(dist.contents)
87
97
  .sort(([a], [b]) => a.localeCompare(b))
@@ -91,10 +101,12 @@ function printed(dist: Dist): string {
91
101
 
92
102
  describe("declarations (full ember() via tsdown)", () => {
93
103
  it("emits .d.ts for an entry that type-imports from a non-entry .gts", async () => {
94
- // Mirrors a design-system shape: the component is in the runtime graph via
95
- // the index entry, while a second entry (a service) only *type*-imports
96
- // from it. The service's declaration then references the component's
97
- // (virtual) .d.ts, which only exists in the bundler's module graph.
104
+ // Mirrors a design-system shape:
105
+ // - the component is in the runtime graph via the index entry
106
+ // - a second entry (a service) only *type*-imports from it
107
+ //
108
+ // The service's declaration then references the component's (virtual) .d.ts,
109
+ // which only exists in the bundler's module graph.
98
110
  const dist = await buildFixture(
99
111
  {
100
112
  "src/index.ts": `export { default as Popup } from './popup.gts';`,
@@ -174,11 +186,12 @@ describe("declarations (full ember() via tsdown)", () => {
174
186
  });
175
187
 
176
188
  it("emits .d.ts when a .gts module is only ever type-imported", async () => {
177
- // Nothing value-imports popup.gts, so its virtual module never enters the
178
- // runtime graph: the only route to its declaration is the dts resolver
179
- // loading it on demand. The declaration pipeline registers modules as
180
- // their (virtual) source loads, so the import must resolve to the source
181
- // id — a pre-rewritten `.d.ts` id skips that registration and dangles.
189
+ // Nothing value-imports popup.gts, so its virtual module never enters the runtime graph.
190
+ // The only route to its declaration is the dts resolver loading it on demand.
191
+ //
192
+ // The declaration pipeline registers modules as their (virtual) source loads,
193
+ // so the import must resolve to the source id.
194
+ // A pre-rewritten `.d.ts` id skips that registration and dangles.
182
195
  const dist = await buildFixture(
183
196
  {
184
197
  "src/service.ts": [
@@ -233,10 +246,10 @@ describe("declarations (full ember() via tsdown)", () => {
233
246
  });
234
247
 
235
248
  it("emits .d.ts for a .gts that type-imports another (otherwise unimported) .gts", async () => {
236
- // A nested component type-imports a sibling from the parent directory;
237
- // its declaration then carries a relative source specifier for a module
238
- // that exists only in the bundler's graph (never on disk, never in the
239
- // runtime graph).
249
+ // A nested component type-imports a sibling from the parent directory.
250
+ //
251
+ // Its declaration then carries a relative source specifier for a module
252
+ // that exists only in the bundler's graph (never on disk, never in the runtime graph).
240
253
  const dist = await buildFixture(
241
254
  {
242
255
  "src/index.ts": `export { default as OptionList } from './components/select/option-list.gts';`,
@@ -288,11 +301,14 @@ describe("declarations (full ember() via tsdown)", () => {
288
301
  });
289
302
 
290
303
  it("bundles co-located CSS (via @tsdown/css) without breaking declarations", async () => {
291
- // Ember components routinely import a co-located .css file. Without CSS
292
- // handling the build dies on tsdown's css-guard — and because the failing
293
- // component module never loads, every declaration that references it
294
- // dangles (surfacing as misleading UNLOADABLE_DEPENDENCY errors on
295
- // <component>.d.ts). @tsdown/css makes both work.
304
+ // Ember components routinely import a co-located .css file.
305
+ //
306
+ // Without CSS handling, the build dies on tsdown's css-guard.
307
+ // And because the failing component module never loads,
308
+ // every declaration that references it dangles
309
+ // (surfacing as misleading UNLOADABLE_DEPENDENCY errors on <component>.d.ts).
310
+ //
311
+ // @tsdown/css makes both work.
296
312
  const dist = await buildFixture(
297
313
  {
298
314
  "src/index.ts": `export { default as Popup } from './popup.gts';`,
@@ -14,8 +14,9 @@ afterEach(() => {
14
14
  });
15
15
 
16
16
  /**
17
- * An ember-source stub whose `ember-addon.renamed-modules` provides
18
- * `@glimmer/runtime` (file-path keys, like the real manifest).
17
+ * An ember-source stub whose `ember-addon.renamed-modules` provides `@glimmer/runtime`.
18
+ *
19
+ * File-path keys, like the real manifest.
19
20
  */
20
21
  const EMBER_SOURCE_STUB = {
21
22
  name: "ember-source",
@@ -29,9 +30,11 @@ const EMBER_SOURCE_STUB = {
29
30
  };
30
31
 
31
32
  /**
32
- * Writes a fixture package (manifest + optional stub ember-source in its
33
- * node_modules) and cds into it — the plugin reads both from the library's
34
- * own context, like the tsdown CLI.
33
+ * Writes a fixture package and cds into it:
34
+ * - a manifest
35
+ * - an optional stub ember-source in its node_modules
36
+ *
37
+ * The plugin reads both from the library's own context, like the tsdown CLI.
35
38
  */
36
39
  async function fixture(manifest: object, { emberSource = true } = {}): Promise<string> {
37
40
  const dir = await mkdtemp(path.join(tmpdir(), "ember-rolldown-externals-"));
@@ -50,7 +53,9 @@ async function fixture(manifest: object, { emberSource = true } = {}): Promise<s
50
53
  return dir;
51
54
  }
52
55
 
53
- /** Runs the plugin's own hooks directly against the current fixture. */
56
+ /**
57
+ * Runs the plugin's own hooks directly against the current fixture.
58
+ */
54
59
  function resolveWith(source: string): unknown {
55
60
  const plugin = emberExternals();
56
61
  // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-explicit-any
@@ -68,17 +73,17 @@ describe("emberExternals", () => {
68
73
  });
69
74
 
70
75
  it("externalizes the modules ember-source provides via renamed-modules", async () => {
71
- // Private API like @glimmer/runtime exists only inside the app's
72
- // ember-source (its ember-addon.renamed-modules) — @embroider/core's
73
- // emberVirtualPackages doesn't list it.
76
+ // Private API like @glimmer/runtime exists only inside the app's ember-source
77
+ // (its ember-addon.renamed-modules).
78
+ // @embroider/core's emberVirtualPackages doesn't list it.
74
79
  await fixture({});
75
80
  expect(resolveWith("@glimmer/runtime")).toBe(false);
76
81
  expect(resolveWith("@ember/-internals/glimmer")).toBe(false);
77
82
  });
78
83
 
79
84
  it("does not blanket-externalize @glimmer packages ember-source doesn't provide", async () => {
80
- // e.g. @glimmer/component is a real package a library may want bundled;
81
- // only declared deps / virtual packages / renamed-modules are external.
85
+ // e.g. @glimmer/component is a real package a library may want bundled.
86
+ // Only declared deps / virtual packages / renamed-modules are external.
82
87
  await fixture({});
83
88
  expect(resolveWith("@glimmer/not-provided")).toBeUndefined();
84
89
  });
@@ -18,8 +18,8 @@ function readJsonSync(path: string) {
18
18
  }
19
19
 
20
20
  /**
21
- * Everything the library declares as a `dependency` or `peerDependency` is
22
- * resolvable by the consuming app, so we never bundle it.
21
+ * Everything the library declares as a `dependency` or `peerDependency`
22
+ * is resolvable by the consuming app, so we never bundle it.
23
23
  */
24
24
  function resolvableDependencies(): Set<string> {
25
25
  const deps = new Set<string>();
@@ -79,10 +79,13 @@ function emberSourceProvidedModules(): Set<string> {
79
79
  }
80
80
 
81
81
  /**
82
- * Keeps the library's declared dependencies and the ember virtual packages
83
- * (things like `@ember/component`, `@glimmer/tracking`, the template compiler,
84
- * …) external, so the app that consumes the library resolves them instead of
85
- * the library bundling copies of them.
82
+ * Keeps external:
83
+ * - the library's declared dependencies
84
+ * - the ember virtual packages
85
+ * (things like `@ember/component`, `@glimmer/tracking`, the template compiler, …)
86
+ *
87
+ * So the app that consumes the library resolves them,
88
+ * instead of the library bundling copies of them.
86
89
  */
87
90
  export function emberExternals(): Plugin {
88
91
  let deps: Set<string>;
@@ -100,8 +103,8 @@ export function emberExternals(): Plugin {
100
103
  resolveId: {
101
104
  order: "pre",
102
105
  handler(source) {
103
- // Anything with a protocol (`node:`, virtual modules, …) is not ours
104
- // to externalize.
106
+ // Anything with a protocol (`node:`, virtual modules, …)
107
+ // is not ours to externalize.
105
108
  if (source.includes(":")) {
106
109
  return null;
107
110
  }
@@ -124,10 +127,13 @@ export function emberExternals(): Plugin {
124
127
  }
125
128
 
126
129
  // Modules ember-source provides by renaming them into itself
127
- // (`ember-addon.renamed-modules`) — private API like
128
- // `@glimmer/runtime` that only exists inside the app's ember-source.
129
- // Matched on the full specifier: the renamed modules are module
130
- // paths, not packages.
130
+ // (`ember-addon.renamed-modules`).
131
+ //
132
+ // Private API like `@glimmer/runtime`,
133
+ // which only exists inside the app's ember-source.
134
+ //
135
+ // Matched on the full specifier:
136
+ // the renamed modules are module paths, not packages.
131
137
  if (renamedModules.has(source)) {
132
138
  return false;
133
139
  }