ember.nvp 0.7.0 → 1.1.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 (47) hide show
  1. package/.github/workflows/ci.yml +6 -0
  2. package/.release-plan.json +7 -59
  3. package/CHANGELOG.md +46 -0
  4. package/package.json +1 -1
  5. package/packages/rolldown/README.md +68 -1
  6. package/packages/rolldown/index.ts +8 -4
  7. package/packages/rolldown/package.json +13 -4
  8. package/packages/rolldown/rolldown.config.js +5 -3
  9. package/packages/rolldown/src/app-reexports.test.ts +220 -0
  10. package/packages/rolldown/src/app-reexports.ts +152 -0
  11. package/packages/rolldown/src/config.ts +50 -0
  12. package/packages/rolldown/src/dts.test.ts +166 -0
  13. package/packages/rolldown/src/transform.test.ts +173 -0
  14. package/src/bases/minimal-extension/extension.test.ts +251 -0
  15. package/src/bases/minimal-extension/files/.env.development +8 -0
  16. package/src/bases/minimal-extension/files/README.md +36 -0
  17. package/src/bases/minimal-extension/files/app/app.ts +21 -0
  18. package/src/bases/minimal-extension/files/app/boot.ts +8 -0
  19. package/src/bases/minimal-extension/files/app/config.ts +22 -0
  20. package/src/bases/minimal-extension/files/app/router.ts +10 -0
  21. package/src/bases/minimal-extension/files/app/templates/application.gts +5 -0
  22. package/src/bases/minimal-extension/files/babel.config.js +49 -0
  23. package/src/bases/minimal-extension/files/index.html +17 -0
  24. package/src/bases/minimal-extension/files/package.json +58 -0
  25. package/src/bases/minimal-extension/files/public/manifest.json +9 -0
  26. package/src/bases/minimal-extension/files/tsconfig.json +13 -0
  27. package/src/bases/minimal-extension/files/vite.config.mjs +6 -0
  28. package/src/bases/minimal-extension/index.js +142 -0
  29. package/src/bases/minimal-library/files/tsdown.config.js +2 -1
  30. package/src/cli/generator.js +4 -0
  31. package/src/cli/questions/project-type.js +9 -2
  32. package/src/layers/renovate/index.js +4 -3
  33. package/src/layers/typescript/index.js +6 -0
  34. package/src/utils/types.ts +1 -1
  35. package/test/ember.nvp/base-library.test.ts +41 -30
  36. package/test/ember.nvp/layer-publish-checks.test.ts +7 -10
  37. package/test/ember.nvp/layer-qunit.test.ts +17 -30
  38. package/test/ember.nvp/layer-vitest.test.ts +4 -18
  39. package/test/ember.nvp/permutations/app-checks.test.ts +3 -0
  40. package/test/ember.nvp/permutations/extension-1.test.ts +3 -0
  41. package/test/ember.nvp/permutations/extension-2.test.ts +3 -0
  42. package/test/ember.nvp/permutations/extension-checks.test.ts +3 -0
  43. package/test/ember.nvp/permutations/library-checks.test.ts +3 -0
  44. package/test/ember.nvp/permutations/shared.ts +68 -19
  45. package/test/test-helpers.ts +25 -5
  46. package/vitest.config.js +1 -1
  47. package/packages/rolldown/src/define-config.ts +0 -32
@@ -45,6 +45,12 @@ jobs:
45
45
  args: test/ember.nvp/permutations/library-1.test.ts
46
46
  - name: library permutations 2/2
47
47
  args: test/ember.nvp/permutations/library-2.test.ts
48
+ - name: extension permutations 1/2
49
+ args: test/ember.nvp/permutations/extension-1.test.ts
50
+ - name: extension permutations 2/2
51
+ args: test/ember.nvp/permutations/extension-2.test.ts
52
+ - name: check-layer permutations
53
+ args: test/ember.nvp/permutations/app-checks.test.ts test/ember.nvp/permutations/library-checks.test.ts test/ember.nvp/permutations/extension-checks.test.ts
48
54
  - name: everything else
49
55
  args: --exclude 'test/ember.nvp/permutations/**'
50
56
 
@@ -2,18 +2,14 @@
2
2
  "solution": {
3
3
  "ember.nvp": {
4
4
  "impact": "minor",
5
- "oldVersion": "0.6.0",
6
- "newVersion": "0.7.0",
5
+ "oldVersion": "1.0.0",
6
+ "newVersion": "1.1.0",
7
7
  "tagName": "latest",
8
8
  "constraints": [
9
9
  {
10
10
  "impact": "minor",
11
11
  "reason": "Appears in changelog section :rocket: Enhancement"
12
12
  },
13
- {
14
- "impact": "patch",
15
- "reason": "Appears in changelog section :bug: Bug Fix"
16
- },
17
13
  {
18
14
  "impact": "patch",
19
15
  "reason": "Appears in changelog section :memo: Documentation"
@@ -26,44 +22,18 @@
26
22
  "pkgJSONPath": "./package.json"
27
23
  },
28
24
  "@nullvoxpopuli/ember-build-tooling-utils": {
29
- "impact": "minor",
30
- "oldVersion": "1.0.0",
31
- "newVersion": "1.1.0",
32
- "tagName": "latest",
33
- "constraints": [
34
- {
35
- "impact": "minor",
36
- "reason": "Appears in changelog section :rocket: Enhancement"
37
- },
38
- {
39
- "impact": "patch",
40
- "reason": "Appears in changelog section :bug: Bug Fix"
41
- },
42
- {
43
- "impact": "patch",
44
- "reason": "Appears in changelog section :memo: Documentation"
45
- }
46
- ],
47
- "pkgJSONPath": "./packages/build-tooling-utils/package.json"
25
+ "oldVersion": "1.1.0"
48
26
  },
49
27
  "@nullvoxpopuli/ember-rolldown": {
50
28
  "impact": "minor",
51
- "oldVersion": "1.0.0",
52
- "newVersion": "1.1.0",
29
+ "oldVersion": "2.0.0",
30
+ "newVersion": "2.1.0",
53
31
  "tagName": "latest",
54
32
  "constraints": [
55
- {
56
- "impact": "patch",
57
- "reason": "Has dependency `workspace:*` on @nullvoxpopuli/ember-build-tooling-utils"
58
- },
59
33
  {
60
34
  "impact": "minor",
61
35
  "reason": "Appears in changelog section :rocket: Enhancement"
62
36
  },
63
- {
64
- "impact": "patch",
65
- "reason": "Appears in changelog section :bug: Bug Fix"
66
- },
67
37
  {
68
38
  "impact": "patch",
69
39
  "reason": "Appears in changelog section :memo: Documentation"
@@ -76,30 +46,8 @@
76
46
  "pkgJSONPath": "./packages/rolldown/package.json"
77
47
  },
78
48
  "@nullvoxpopuli/ember-vite": {
79
- "impact": "minor",
80
- "oldVersion": "1.0.3",
81
- "newVersion": "1.1.0",
82
- "tagName": "latest",
83
- "constraints": [
84
- {
85
- "impact": "minor",
86
- "reason": "Appears in changelog section :rocket: Enhancement"
87
- },
88
- {
89
- "impact": "patch",
90
- "reason": "Has dependency `workspace:*` on @nullvoxpopuli/ember-build-tooling-utils"
91
- },
92
- {
93
- "impact": "patch",
94
- "reason": "Appears in changelog section :bug: Bug Fix"
95
- },
96
- {
97
- "impact": "patch",
98
- "reason": "Appears in changelog section :house: Internal"
99
- }
100
- ],
101
- "pkgJSONPath": "./packages/vite/package.json"
49
+ "oldVersion": "1.1.0"
102
50
  }
103
51
  },
104
- "description": "## Release (2026-07-17)\n\n* ember.nvp 0.7.0 (minor)\n* @nullvoxpopuli/ember-build-tooling-utils 1.1.0 (minor)\n* @nullvoxpopuli/ember-rolldown 1.1.0 (minor)\n* @nullvoxpopuli/ember-vite 1.1.0 (minor)\n\n#### :rocket: Enhancement\n* `ember.nvp`\n * [#78](https://github.com/NullVoxPopuli/ember.nvp/pull/78) publint and are-the-types-wrong layers ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n * [#77](https://github.com/NullVoxPopuli/ember.nvp/pull/77) TypeScript is the default for libraries ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n * [#73](https://github.com/NullVoxPopuli/ember.nvp/pull/73) Implement the vitest layer for libraries ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n * [#71](https://github.com/NullVoxPopuli/ember.nvp/pull/71) Add inspector-support layer (Ember Inspector wiring via ember-estree codemod) ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n* `@nullvoxpopuli/ember-vite`, `ember.nvp`\n * [#72](https://github.com/NullVoxPopuli/ember.nvp/pull/72) qunit layer: support libraries (tests without any index.html) ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n * [#61](https://github.com/NullVoxPopuli/ember.nvp/pull/61) Update maybeBabel ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n* `ember.nvp`, `@nullvoxpopuli/ember-build-tooling-utils`, `@nullvoxpopuli/ember-rolldown`, `@nullvoxpopuli/ember-vite`\n * [#63](https://github.com/NullVoxPopuli/ember.nvp/pull/63) Library support via rolldown ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n * [#66](https://github.com/NullVoxPopuli/ember.nvp/pull/66) Lean on node 24: RegExp.escape, import.meta.dirname, fs.rm ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n* `@nullvoxpopuli/ember-rolldown`, `ember.nvp`\n * [#67](https://github.com/NullVoxPopuli/ember.nvp/pull/67) Export our own defineConfig, preloaded with ember library defaults ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n * [#64](https://github.com/NullVoxPopuli/ember.nvp/pull/64) Make both library flavors build: isolated declarations, hbs targetFormat, real tests ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n* `@nullvoxpopuli/ember-build-tooling-utils`, `@nullvoxpopuli/ember-rolldown`, `ember.nvp`\n * [#65](https://github.com/NullVoxPopuli/ember.nvp/pull/65) No generated babel.config.js; drop the plugin packages' dist build ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n* `@nullvoxpopuli/ember-vite`\n * [#62](https://github.com/NullVoxPopuli/ember.nvp/pull/62) Cleanup maybeBabel ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### :bug: Bug Fix\n* `@nullvoxpopuli/ember-build-tooling-utils`, `@nullvoxpopuli/ember-rolldown`, `ember.nvp`\n * [#83](https://github.com/NullVoxPopuli/ember.nvp/pull/83) Can't publish TS, build with rolldown ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n* `@nullvoxpopuli/ember-rolldown`\n * [#82](https://github.com/NullVoxPopuli/ember.nvp/pull/82) Fix watching in the rolldown plugin ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n* `ember.nvp`\n * [#79](https://github.com/NullVoxPopuli/ember.nvp/pull/79) The library base generates an empty src ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n * [#74](https://github.com/NullVoxPopuli/ember.nvp/pull/74) Generated TS libraries type check: drop allowJs, load ember/glint types ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n * [#70](https://github.com/NullVoxPopuli/ember.nvp/pull/70) Library template is private; generated libraries are publishable ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n * [#58](https://github.com/NullVoxPopuli/ember.nvp/pull/58) Fix lint:types in generated qunit apps: import #app/app.ts with extension ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n * [#59](https://github.com/NullVoxPopuli/ember.nvp/pull/59) Make freshly generated apps pass their own pnpm lint ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n* `@nullvoxpopuli/ember-vite`, `ember.nvp`\n * [#75](https://github.com/NullVoxPopuli/ember.nvp/pull/75) packages/vite: built-in babel fallback when no config file exists ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n\n#### :memo: Documentation\n* `@nullvoxpopuli/ember-build-tooling-utils`, `@nullvoxpopuli/ember-rolldown`, `ember.nvp`\n * [#68](https://github.com/NullVoxPopuli/ember.nvp/pull/68) Docs pass: outcome-focused, timeless READMEs and comments ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n\n#### :house: Internal\n* `ember.nvp`\n * [#81](https://github.com/NullVoxPopuli/ember.nvp/pull/81) Split the app permutations CI slice in two ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n * [#80](https://github.com/NullVoxPopuli/ember.nvp/pull/80) Split the app and library permutations CI slices in two ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n * [#76](https://github.com/NullVoxPopuli/ember.nvp/pull/76) Shard CI: per-base permutation files, parallel Node Tests jobs ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n* `@nullvoxpopuli/ember-rolldown`, `@nullvoxpopuli/ember-vite`, `ember.nvp`\n * [#69](https://github.com/NullVoxPopuli/ember.nvp/pull/69) Remove comments from config files (generated and in READMEs) ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n\n#### Committers: 2\n- @NullVoxPopuli's reduced-access machine account for AI usage ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n"
52
+ "description": "## Release (2026-07-22)\n\n* ember.nvp 1.1.0 (minor)\n* @nullvoxpopuli/ember-rolldown 2.1.0 (minor)\n\n#### :rocket: Enhancement\n* `@nullvoxpopuli/ember-rolldown`\n * [#97](https://github.com/NullVoxPopuli/ember.nvp/pull/97) Add appReexports plugin as a separate ember-rolldown entrypoint ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n * [#95](https://github.com/NullVoxPopuli/ember.nvp/pull/95) enable declaration maps in the rolldown plugin ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n* `ember.nvp`\n * [#91](https://github.com/NullVoxPopuli/ember.nvp/pull/91) Add a browser extension project type (minimal-extension base) ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n\n#### :memo: Documentation\n* `@nullvoxpopuli/ember-rolldown`, `ember.nvp`\n * [#96](https://github.com/NullVoxPopuli/ember.nvp/pull/96) rolldown: document + test co-located CSS support ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### :house: Internal\n* `@nullvoxpopuli/ember-rolldown`, `ember.nvp`\n * [#93](https://github.com/NullVoxPopuli/ember.nvp/pull/93) More tests ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n* `ember.nvp`\n * [#92](https://github.com/NullVoxPopuli/ember.nvp/pull/92) Move check layers into their own permutation matrix ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n * [#90](https://github.com/NullVoxPopuli/ember.nvp/pull/90) Remove pinYukuParser test helper ([@Copilot](https://github.com/apps/copilot-swe-agent))\n * [#89](https://github.com/NullVoxPopuli/ember.nvp/pull/89) refactor(test): extract shared listFiles/read helpers to #test-helpers ([@Copilot](https://github.com/apps/copilot-swe-agent))\n * [#87](https://github.com/NullVoxPopuli/ember.nvp/pull/87) Convert Greeting declaration check to inline snapshot assertion ([@Copilot](https://github.com/apps/copilot-swe-agent))\n\n#### Committers: 3\n- @NullVoxPopuli's reduced-access machine account for AI usage ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n- Copilot [Bot] ([@copilot-swe-agent](https://github.com/apps/copilot-swe-agent))\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n"
105
53
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # Changelog
2
2
 
3
+ ## Release (2026-07-22)
4
+
5
+ * ember.nvp 1.1.0 (minor)
6
+ * @nullvoxpopuli/ember-rolldown 2.1.0 (minor)
7
+
8
+ #### :rocket: Enhancement
9
+ * `@nullvoxpopuli/ember-rolldown`
10
+ * [#97](https://github.com/NullVoxPopuli/ember.nvp/pull/97) Add appReexports plugin as a separate ember-rolldown entrypoint ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))
11
+ * [#95](https://github.com/NullVoxPopuli/ember.nvp/pull/95) enable declaration maps in the rolldown plugin ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
12
+ * `ember.nvp`
13
+ * [#91](https://github.com/NullVoxPopuli/ember.nvp/pull/91) Add a browser extension project type (minimal-extension base) ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))
14
+
15
+ #### :memo: Documentation
16
+ * `@nullvoxpopuli/ember-rolldown`, `ember.nvp`
17
+ * [#96](https://github.com/NullVoxPopuli/ember.nvp/pull/96) rolldown: document + test co-located CSS support ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
18
+
19
+ #### :house: Internal
20
+ * `@nullvoxpopuli/ember-rolldown`, `ember.nvp`
21
+ * [#93](https://github.com/NullVoxPopuli/ember.nvp/pull/93) More tests ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
22
+ * `ember.nvp`
23
+ * [#92](https://github.com/NullVoxPopuli/ember.nvp/pull/92) Move check layers into their own permutation matrix ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))
24
+ * [#90](https://github.com/NullVoxPopuli/ember.nvp/pull/90) Remove pinYukuParser test helper ([@Copilot](https://github.com/apps/copilot-swe-agent))
25
+ * [#89](https://github.com/NullVoxPopuli/ember.nvp/pull/89) refactor(test): extract shared listFiles/read helpers to #test-helpers ([@Copilot](https://github.com/apps/copilot-swe-agent))
26
+ * [#87](https://github.com/NullVoxPopuli/ember.nvp/pull/87) Convert Greeting declaration check to inline snapshot assertion ([@Copilot](https://github.com/apps/copilot-swe-agent))
27
+
28
+ #### Committers: 3
29
+ - @NullVoxPopuli's reduced-access machine account for AI usage ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))
30
+ - Copilot [Bot] ([@copilot-swe-agent](https://github.com/apps/copilot-swe-agent))
31
+ - [@NullVoxPopuli](https://github.com/NullVoxPopuli)
32
+
33
+ ## Release (2026-07-18)
34
+
35
+ * ember.nvp 1.0.0 (major)
36
+ * @nullvoxpopuli/ember-rolldown 2.0.0 (major)
37
+
38
+ #### :boom: Breaking Change
39
+ * `@nullvoxpopuli/ember-rolldown`, `ember.nvp`
40
+ * [#86](https://github.com/NullVoxPopuli/ember.nvp/pull/86) Remove defineConfig ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
41
+
42
+ #### :rocket: Enhancement
43
+ * `@nullvoxpopuli/ember-rolldown`, `ember.nvp`
44
+ * [#84](https://github.com/NullVoxPopuli/ember.nvp/pull/84) Create config plugin for rolldown ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
45
+
46
+ #### Committers: 1
47
+ - [@NullVoxPopuli](https://github.com/NullVoxPopuli)
48
+
3
49
  ## Release (2026-07-17)
4
50
 
5
51
  * ember.nvp 0.7.0 (minor)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ember.nvp",
3
- "version": "0.7.0",
3
+ "version": "1.1.0",
4
4
  "description": "ember project generator, alternative to the official blueprints, but more modern -- hopefully one day to upstream back in to ember-cli / official blueprints",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,10 +20,15 @@ modern TypeScript when type-checking: 6+ with `lib` covering `es2025` (e.g.
20
20
 
21
21
  ## Usage
22
22
 
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`).
26
+
23
27
  In your `tsdown.config.js` (recommended — emits declarations):
24
28
 
25
29
  ```js
26
- import { defineConfig, ember } from "@nullvoxpopuli/ember-rolldown";
30
+ import { defineConfig } from "tsdown";
31
+ import { ember } from "@nullvoxpopuli/ember-rolldown";
27
32
 
28
33
  export default defineConfig({
29
34
  entry: ["./src/index.ts"],
@@ -77,6 +82,27 @@ import type { TOC } from "@ember/component/template-only";
77
82
  export const Badge: TOC<BadgeSignature> = <template>...</template>;
78
83
  ```
79
84
 
85
+ ## CSS
86
+
87
+ Components that import co-located CSS (`import './popup.css'`) need
88
+ [`@tsdown/css`](https://www.npmjs.com/package/@tsdown/css) installed in your
89
+ library — tsdown auto-detects it and bundles every imported stylesheet into a
90
+ single CSS file in `dist/`. Install the version matching your `tsdown`
91
+ version (they're released in lockstep):
92
+
93
+ ```bash
94
+ npm add --save-dev @tsdown/css
95
+ ```
96
+
97
+ Without it, tsdown's css-guard fails the build on the first CSS import — and
98
+ because the importing component module never loads, any declaration that
99
+ references that component dangles, surfacing as misleading
100
+ `UNLOADABLE_DEPENDENCY` errors on `<component>.d.ts` files.
101
+
102
+ Scoping/transforming the CSS itself (e.g.
103
+ [ember-scoped-css](https://github.com/soxhub/ember-scoped-css)) is separate
104
+ from bundling and runs via its own plugins.
105
+
80
106
  ## What `ember()` does
81
107
 
82
108
  `ember()` returns an array of rolldown plugins:
@@ -109,6 +135,47 @@ ember({
109
135
 
110
136
  Each option is documented on `BabelOptions`.
111
137
 
138
+ ## App re-exports
139
+
140
+ For libraries whose modules must appear in the consuming app's namespace
141
+ (classic resolution: `{{a-component}}`, services and helpers looked up by
142
+ name), `appReexports()` does the same job as `@embroider/addon-dev`'s
143
+ `appReexports` rollup plugin. It's a separate import because most libraries
144
+ don't need it:
145
+
146
+ ```js
147
+ import { defineConfig } from "tsdown";
148
+ import { ember } from "@nullvoxpopuli/ember-rolldown";
149
+ import { appReexports } from "@nullvoxpopuli/ember-rolldown/app-reexports";
150
+
151
+ export default defineConfig({
152
+ entry: ["./src/index.ts", "./src/services/session.ts"],
153
+ plugins: [ember(), appReexports()],
154
+ });
155
+ ```
156
+
157
+ With no arguments, top-level services (`services/*`) are re-exported — under
158
+ strict mode, components and helpers are imported, but services are still
159
+ injected by name. A string argument is an include glob, and an object gives
160
+ full control:
161
+
162
+ ```js
163
+ appReexports(); // services/*
164
+ appReexports("components/**"); // one include glob
165
+ appReexports({ include: ["services/*", "helpers/*"], exclude: [...] });
166
+ ```
167
+
168
+ For every built file matching `include` (minus `exclude` and `.d.ts` files),
169
+ it writes a module under `dist/_app_/` re-exporting from the library's own
170
+ name, and records the set in `package.json` under `ember-addon.app-js`.
171
+ `mapFilename` renames a re-export; `exports` picks which bindings it forwards
172
+ (default `["default"]`).
173
+
174
+ Unlike the embroider plugin, nothing is written unless its content actually
175
+ differs from what is on disk — `_app_/` modules are compared byte-for-byte
176
+ and `package.json` is only rewritten when the `app-js` map itself changed —
177
+ so rebuilds don't re-trigger file watchers.
178
+
112
179
  ## Credit
113
180
 
114
181
  The `emberExternals` and `emberTransform` plugins are derived from
@@ -1,12 +1,11 @@
1
1
  import type { Plugin } from "rolldown";
2
2
 
3
3
  import { emberBabel, type BabelOptions } from "./src/babel.ts";
4
+ import { emberConfig } from "./src/config.ts";
4
5
  import { emberExternals } from "./src/externals.ts";
5
6
  import { emberIsolatedDeclarations } from "./src/isolated-declarations.ts";
6
7
  import { emberTransform } from "./src/transform.ts";
7
8
 
8
- export { defineConfig } from "./src/define-config.ts";
9
-
10
9
  interface Config {
11
10
  /**
12
11
  * Options for the babel step; see `BabelOptions`.
@@ -21,6 +20,9 @@ interface Config {
21
20
  * It bundles everything needed to compile `.gts`/`.gjs` and template-tag
22
21
  * (`<template>`) source into publishable output:
23
22
  *
23
+ * - `emberConfig()` — applies sensible tsdown defaults for a library build
24
+ * (sourcemaps, `clean`, `dts`, `.js`/`.d.ts` extensions, quiet logging);
25
+ * anything you set explicitly still wins.
24
26
  * - `emberIsolatedDeclarations()` — errors when a tsconfig.json is present
25
27
  * without `isolatedDeclarations: true` (required: it is the only
26
28
  * declaration pipeline that can see compiled template-tag modules).
@@ -34,20 +36,22 @@ interface Config {
34
36
  * native transform. The library's own `babel.config.js` is used when it
35
37
  * exists; no config file is required.
36
38
  *
37
- * Usage in `tsdown.config.js` / `rolldown.config.js`:
39
+ * Usage in `tsdown.config.js` (import `defineConfig` from `tsdown` — or from
40
+ * `rolldown` for a `rolldown.config.js` — so it carries the correct types):
38
41
  *
39
42
  * ```js
43
+ * import { defineConfig } from "tsdown";
40
44
  * import { ember } from "@nullvoxpopuli/ember-rolldown";
41
45
  *
42
46
  * export default defineConfig({
43
47
  * entry: ["./src/index.ts"],
44
- * dts: true,
45
48
  * plugins: [ember()],
46
49
  * });
47
50
  * ```
48
51
  */
49
52
  export function ember(config: Config = {}): Plugin[] {
50
53
  return [
54
+ emberConfig(),
51
55
  emberIsolatedDeclarations(),
52
56
  emberExternals(),
53
57
  emberTransform(),
@@ -1,11 +1,19 @@
1
1
  {
2
2
  "name": "@nullvoxpopuli/ember-rolldown",
3
- "version": "1.1.0",
3
+ "version": "2.1.0",
4
4
  "description": "Meta plugin for building ember v2 libraries/addons with rolldown & tsdown.",
5
5
  "type": "module",
6
6
  "main": "index.ts",
7
+ "exports": {
8
+ ".": "./index.ts",
9
+ "./app-reexports": "./src/app-reexports.ts"
10
+ },
7
11
  "publishConfig": {
8
- "main": "dist/index.js"
12
+ "main": "dist/index.js",
13
+ "exports": {
14
+ ".": "./dist/index.js",
15
+ "./app-reexports": "./dist/app-reexports.js"
16
+ }
9
17
  },
10
18
  "keywords": [
11
19
  "rolldown",
@@ -52,8 +60,9 @@
52
60
  }
53
61
  },
54
62
  "devDependencies": {
55
- "rolldown": "^1.1.2",
56
- "tsdown": "^0.22.7",
63
+ "@tsdown/css": "^0.22.13",
64
+ "rolldown": "^1.2.0",
65
+ "tsdown": "^0.22.9",
57
66
  "typescript": "^6.0.3"
58
67
  }
59
68
  }
@@ -8,11 +8,13 @@ const deps = [
8
8
  ];
9
9
 
10
10
  export default defineConfig({
11
- input: "index.ts",
11
+ input: {
12
+ index: "index.ts",
13
+ "app-reexports": "src/app-reexports.ts",
14
+ },
12
15
  output: {
13
16
  format: "esm",
14
- file: "dist/index.js",
15
- codeSplitting: false,
17
+ dir: "dist",
16
18
  },
17
19
  external(id) {
18
20
  if (id.startsWith("node:")) return true;
@@ -0,0 +1,220 @@
1
+ import { mkdir, mkdtemp, readFile, rm, stat, utimes, writeFile } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+
5
+ import { rolldown, type Plugin } from "rolldown";
6
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
7
+
8
+ import { appReexports, type AppReexportsOptions } from "./app-reexports.ts";
9
+
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.
14
+ */
15
+ describe("appReexports", () => {
16
+ let projectDir: string;
17
+ const originalCwd = process.cwd();
18
+
19
+ beforeEach(async () => {
20
+ projectDir = await mkdtemp(join(tmpdir(), "app-reexports-"));
21
+
22
+ await writeFile(
23
+ join(projectDir, "package.json"),
24
+ JSON.stringify(
25
+ {
26
+ name: "my-lib",
27
+ version: "1.0.0",
28
+ "ember-addon": { version: 2, type: "addon" },
29
+ },
30
+ null,
31
+ 2,
32
+ ) + "\n",
33
+ );
34
+
35
+ await mkdir(join(projectDir, "src/components"), { recursive: true });
36
+ await mkdir(join(projectDir, "src/utils"), { recursive: true });
37
+ await writeFile(
38
+ join(projectDir, "src/components/foo.js"),
39
+ "export default function foo() {}\n",
40
+ );
41
+ await writeFile(
42
+ join(projectDir, "src/utils/math.js"),
43
+ "export function add(a, b) { return a + b; }\n",
44
+ );
45
+
46
+ process.chdir(projectDir);
47
+ });
48
+
49
+ afterEach(async () => {
50
+ process.chdir(originalCwd);
51
+ await rm(projectDir, { recursive: true, force: true });
52
+ });
53
+
54
+ async function build(options?: string | AppReexportsOptions) {
55
+ const bundle = await rolldown({
56
+ input: {
57
+ "components/foo": "./src/components/foo.js",
58
+ "utils/math": "./src/utils/math.js",
59
+ },
60
+ plugins: [appReexports(options)],
61
+ });
62
+
63
+ await bundle.write({ dir: "dist" });
64
+ await bundle.close();
65
+ }
66
+
67
+ async function readJson(path: string) {
68
+ return JSON.parse(await readFile(path, "utf8"));
69
+ }
70
+
71
+ it("writes _app_ re-exports and records them in package.json", async () => {
72
+ await build({ include: ["components/**"] });
73
+
74
+ expect(await readFile("dist/_app_/components/foo.js", "utf8")).toMatchInlineSnapshot(`
75
+ "export { default } from "my-lib/components/foo";
76
+ "
77
+ `);
78
+
79
+ const manifest = await readJson("package.json");
80
+
81
+ expect(manifest["ember-addon"]).toMatchInlineSnapshot(`
82
+ {
83
+ "app-js": {
84
+ "./components/foo.js": "./dist/_app_/components/foo.js",
85
+ },
86
+ "type": "addon",
87
+ "version": 2,
88
+ }
89
+ `);
90
+
91
+ // utils/math.js didn't match include
92
+ await expect(stat("dist/_app_/utils/math.js")).rejects.toThrow();
93
+ });
94
+
95
+ it("accepts a string argument as the include glob", async () => {
96
+ await build("components/**");
97
+
98
+ const manifest = await readJson("package.json");
99
+
100
+ expect(Object.keys(manifest["ember-addon"]["app-js"])).toEqual(["./components/foo.js"]);
101
+ });
102
+
103
+ it("defaults to top-level services", async () => {
104
+ await mkdir(join(projectDir, "src/services/nested"), { recursive: true });
105
+ await writeFile(join(projectDir, "src/services/session.js"), "export default class {}\n");
106
+ await writeFile(join(projectDir, "src/services/nested/deep.js"), "export default class {}\n");
107
+
108
+ const bundle = await rolldown({
109
+ input: {
110
+ "components/foo": "./src/components/foo.js",
111
+ "services/session": "./src/services/session.js",
112
+ "services/nested/deep": "./src/services/nested/deep.js",
113
+ },
114
+ plugins: [appReexports()],
115
+ });
116
+
117
+ await bundle.write({ dir: "dist" });
118
+ await bundle.close();
119
+
120
+ const manifest = await readJson("package.json");
121
+
122
+ // services/* is not deep: nested services (and everything else) stay out
123
+ expect(manifest["ember-addon"]["app-js"]).toEqual({
124
+ "./services/session.js": "./dist/_app_/services/session.js",
125
+ });
126
+ });
127
+
128
+ it("respects exclude", async () => {
129
+ await build({ include: ["**/*.js"], exclude: ["utils/**"] });
130
+
131
+ const manifest = await readJson("package.json");
132
+
133
+ expect(Object.keys(manifest["ember-addon"]["app-js"])).toEqual(["./components/foo.js"]);
134
+ });
135
+
136
+ it("supports mapFilename and custom exports", async () => {
137
+ await build({
138
+ include: ["utils/**"],
139
+ mapFilename: (name) => name.replace("math", "arithmetic"),
140
+ exports: () => ["add"],
141
+ });
142
+
143
+ expect(await readFile("dist/_app_/utils/arithmetic.js", "utf8")).toMatchInlineSnapshot(`
144
+ "export { add } from "my-lib/utils/math";
145
+ "
146
+ `);
147
+
148
+ const manifest = await readJson("package.json");
149
+
150
+ expect(manifest["ember-addon"]["app-js"]).toMatchInlineSnapshot(`
151
+ {
152
+ "./utils/arithmetic.js": "./dist/_app_/utils/arithmetic.js",
153
+ }
154
+ `);
155
+ });
156
+
157
+ it("never re-exports .d.ts files", async () => {
158
+ const emitDeclaration: Plugin = {
159
+ name: "test:emit-declaration",
160
+ generateBundle() {
161
+ this.emitFile({
162
+ type: "asset",
163
+ fileName: "components/foo.d.ts",
164
+ source: "declare const foo: () => void;\nexport default foo;\n",
165
+ });
166
+ },
167
+ };
168
+
169
+ const bundle = await rolldown({
170
+ input: { "components/foo": "./src/components/foo.js" },
171
+ plugins: [emitDeclaration, appReexports({ include: ["**/*"] })],
172
+ });
173
+
174
+ await bundle.write({ dir: "dist" });
175
+ await bundle.close();
176
+
177
+ const manifest = await readJson("package.json");
178
+
179
+ expect(Object.keys(manifest["ember-addon"]["app-js"])).toEqual(["./components/foo.js"]);
180
+ await expect(stat("dist/_app_/components/foo.d.ts")).rejects.toThrow();
181
+ });
182
+
183
+ it("does not touch files whose content would be unchanged", async () => {
184
+ await build({ include: ["components/**"] });
185
+
186
+ const epoch = new Date(0);
187
+
188
+ await utimes("package.json", epoch, epoch);
189
+ await utimes("dist/_app_/components/foo.js", epoch, epoch);
190
+
191
+ await build({ include: ["components/**"] });
192
+
193
+ expect((await stat("package.json")).mtimeMs).toBe(0);
194
+ expect((await stat("dist/_app_/components/foo.js")).mtimeMs).toBe(0);
195
+ });
196
+
197
+ it("leaves an equivalent package.json alone, regardless of formatting", async () => {
198
+ await build({ include: ["components/**"] });
199
+
200
+ // Same app-js map, different formatting: the plugin must not reformat.
201
+ const reformatted =
202
+ JSON.stringify(await readJson("package.json"), null, 4).replace(/\n/g, "\r\n") + "\r\n";
203
+
204
+ await writeFile("package.json", reformatted);
205
+ await build({ include: ["components/**"] });
206
+
207
+ expect(await readFile("package.json", "utf8")).toBe(reformatted);
208
+ });
209
+
210
+ it("rewrites package.json when the app-js map changes", async () => {
211
+ await build({ include: ["components/**"] });
212
+ await build({ include: ["utils/**"] });
213
+
214
+ const manifest = await readJson("package.json");
215
+
216
+ expect(manifest["ember-addon"]["app-js"]).toEqual({
217
+ "./utils/math.js": "./dist/_app_/utils/math.js",
218
+ });
219
+ });
220
+ });