ember.nvp 1.2.0 → 1.4.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.
- package/.release-plan.json +10 -6
- package/CHANGELOG.md +60 -0
- package/README.md +5 -0
- package/package.json +1 -1
- package/packages/rolldown/README.md +116 -8
- package/packages/rolldown/package.json +1 -1
- package/packages/rolldown/src/app-reexports.test.ts +35 -2
- package/packages/rolldown/src/app-reexports.ts +14 -3
- package/packages/rolldown/src/babel.test.ts +125 -2
- package/packages/rolldown/src/babel.ts +52 -7
- package/packages/rolldown/src/externals.test.ts +118 -0
- package/packages/rolldown/src/externals.ts +45 -0
- package/packages/rolldown/src/isolated-declarations.ts +81 -8
- package/packages/rolldown/src/transform.test.ts +53 -2
- package/packages/rolldown/src/transform.ts +23 -2
- package/src/layers/github-actions/index.js +3 -0
- package/src/layers/prettier/index.js +12 -0
- package/src/layers/readme/index.js +176 -0
- package/src/layers/typescript/index.js +11 -0
- package/src/layers/vitest/index.js +11 -0
- package/src/utils/project.js +7 -0
- package/src/utils/types.ts +5 -0
- package/test/ember.nvp/layer-readme.test.ts +195 -0
- package/test/ember.nvp/layers.test.ts +1 -0
- package/test/ember.nvp/publish-configs.test.ts +352 -0
package/.release-plan.json
CHANGED
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
"solution": {
|
|
3
3
|
"ember.nvp": {
|
|
4
4
|
"impact": "minor",
|
|
5
|
-
"oldVersion": "1.
|
|
6
|
-
"newVersion": "1.
|
|
5
|
+
"oldVersion": "1.3.0",
|
|
6
|
+
"newVersion": "1.4.0",
|
|
7
7
|
"tagName": "latest",
|
|
8
8
|
"constraints": [
|
|
9
9
|
{
|
|
10
10
|
"impact": "minor",
|
|
11
11
|
"reason": "Appears in changelog section :rocket: Enhancement"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"impact": "patch",
|
|
15
|
+
"reason": "Appears in changelog section :house: Internal"
|
|
12
16
|
}
|
|
13
17
|
],
|
|
14
18
|
"pkgJSONPath": "./package.json"
|
|
@@ -18,8 +22,8 @@
|
|
|
18
22
|
},
|
|
19
23
|
"@nullvoxpopuli/ember-rolldown": {
|
|
20
24
|
"impact": "minor",
|
|
21
|
-
"oldVersion": "2.
|
|
22
|
-
"newVersion": "2.
|
|
25
|
+
"oldVersion": "2.5.0",
|
|
26
|
+
"newVersion": "2.6.0",
|
|
23
27
|
"tagName": "latest",
|
|
24
28
|
"constraints": [
|
|
25
29
|
{
|
|
@@ -28,7 +32,7 @@
|
|
|
28
32
|
},
|
|
29
33
|
{
|
|
30
34
|
"impact": "patch",
|
|
31
|
-
"reason": "Appears in changelog section :
|
|
35
|
+
"reason": "Appears in changelog section :house: Internal"
|
|
32
36
|
}
|
|
33
37
|
],
|
|
34
38
|
"pkgJSONPath": "./packages/rolldown/package.json"
|
|
@@ -37,5 +41,5 @@
|
|
|
37
41
|
"oldVersion": "1.1.0"
|
|
38
42
|
}
|
|
39
43
|
},
|
|
40
|
-
"description": "## Release (2026-
|
|
44
|
+
"description": "## Release (2026-08-03)\n\n* ember.nvp 1.4.0 (minor)\n* @nullvoxpopuli/ember-rolldown 2.6.0 (minor)\n\n#### :rocket: Enhancement\n* `@nullvoxpopuli/ember-rolldown`, `ember.nvp`\n * [#111](https://github.com/NullVoxPopuli/ember.nvp/pull/111) rolldown: find publish configs in config/ too ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n* `@nullvoxpopuli/ember-rolldown`\n * [#109](https://github.com/NullVoxPopuli/ember.nvp/pull/109) rolldown: prefer babel.publish.config over babel.config ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### :house: Internal\n* `@nullvoxpopuli/ember-rolldown`, `ember.nvp`\n * [#112](https://github.com/NullVoxPopuli/ember.nvp/pull/112) rolldown: test the isolated-declarations guard through generated libraries ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### Committers: 1\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n"
|
|
41
45
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,65 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Release (2026-08-03)
|
|
4
|
+
|
|
5
|
+
* ember.nvp 1.4.0 (minor)
|
|
6
|
+
* @nullvoxpopuli/ember-rolldown 2.6.0 (minor)
|
|
7
|
+
|
|
8
|
+
#### :rocket: Enhancement
|
|
9
|
+
* `@nullvoxpopuli/ember-rolldown`, `ember.nvp`
|
|
10
|
+
* [#111](https://github.com/NullVoxPopuli/ember.nvp/pull/111) rolldown: find publish configs in config/ too ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
|
|
11
|
+
* `@nullvoxpopuli/ember-rolldown`
|
|
12
|
+
* [#109](https://github.com/NullVoxPopuli/ember.nvp/pull/109) rolldown: prefer babel.publish.config over babel.config ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
|
|
13
|
+
|
|
14
|
+
#### :house: Internal
|
|
15
|
+
* `@nullvoxpopuli/ember-rolldown`, `ember.nvp`
|
|
16
|
+
* [#112](https://github.com/NullVoxPopuli/ember.nvp/pull/112) rolldown: test the isolated-declarations guard through generated libraries ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
|
|
17
|
+
|
|
18
|
+
#### Committers: 1
|
|
19
|
+
- [@NullVoxPopuli](https://github.com/NullVoxPopuli)
|
|
20
|
+
|
|
21
|
+
## Release (2026-08-03)
|
|
22
|
+
|
|
23
|
+
* ember.nvp 1.3.0 (minor)
|
|
24
|
+
* @nullvoxpopuli/ember-rolldown 2.5.0 (minor)
|
|
25
|
+
|
|
26
|
+
#### :rocket: Enhancement
|
|
27
|
+
* `ember.nvp`
|
|
28
|
+
* [#106](https://github.com/NullVoxPopuli/ember.nvp/pull/106) README layer ([@tcjr](https://github.com/tcjr))
|
|
29
|
+
* `@nullvoxpopuli/ember-rolldown`
|
|
30
|
+
* [#107](https://github.com/NullVoxPopuli/ember.nvp/pull/107) rolldown: check the tsconfig the build uses, not always tsconfig.json ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
|
|
31
|
+
|
|
32
|
+
#### Committers: 2
|
|
33
|
+
- Tom Carter ([@tcjr](https://github.com/tcjr))
|
|
34
|
+
- [@NullVoxPopuli](https://github.com/NullVoxPopuli)
|
|
35
|
+
|
|
36
|
+
## Release (2026-07-24)
|
|
37
|
+
|
|
38
|
+
* @nullvoxpopuli/ember-rolldown 2.4.0 (minor)
|
|
39
|
+
|
|
40
|
+
#### :rocket: Enhancement
|
|
41
|
+
* `@nullvoxpopuli/ember-rolldown`
|
|
42
|
+
* [#104](https://github.com/NullVoxPopuli/ember.nvp/pull/104) rolldown: support .gts/.gjs build entries ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
|
|
43
|
+
|
|
44
|
+
#### Committers: 1
|
|
45
|
+
- [@NullVoxPopuli](https://github.com/NullVoxPopuli)
|
|
46
|
+
|
|
47
|
+
## Release (2026-07-22)
|
|
48
|
+
|
|
49
|
+
* @nullvoxpopuli/ember-rolldown 2.3.0 (minor)
|
|
50
|
+
|
|
51
|
+
#### :rocket: Enhancement
|
|
52
|
+
* `@nullvoxpopuli/ember-rolldown`
|
|
53
|
+
* [#101](https://github.com/NullVoxPopuli/ember.nvp/pull/101) appReexports: accept string[] include and options as second argument ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))
|
|
54
|
+
|
|
55
|
+
#### :bug: Bug Fix
|
|
56
|
+
* `@nullvoxpopuli/ember-rolldown`
|
|
57
|
+
* [#103](https://github.com/NullVoxPopuli/ember.nvp/pull/103) rolldown: externalize the modules ember-source provides (renamed-modules) ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
|
|
58
|
+
|
|
59
|
+
#### Committers: 2
|
|
60
|
+
- @NullVoxPopuli's reduced-access machine account for AI usage ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))
|
|
61
|
+
- [@NullVoxPopuli](https://github.com/NullVoxPopuli)
|
|
62
|
+
|
|
3
63
|
## Release (2026-07-22)
|
|
4
64
|
|
|
5
65
|
* ember.nvp 1.2.0 (minor)
|
package/README.md
CHANGED
|
@@ -198,6 +198,11 @@ export default {
|
|
|
198
198
|
|
|
199
199
|
await packageJson.addScripts({ "my-script": 'echo "Hello"' }, project.directory);
|
|
200
200
|
},
|
|
201
|
+
|
|
202
|
+
// Optionally add something to the README.md file
|
|
203
|
+
readme() {
|
|
204
|
+
return "### My Feature\n\nHere's what my feature brings to the table.";
|
|
205
|
+
},
|
|
201
206
|
};
|
|
202
207
|
```
|
|
203
208
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ember.nvp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.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",
|
|
@@ -53,16 +53,37 @@ export default defineConfig({
|
|
|
53
53
|
});
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
A
|
|
56
|
+
A babel config is optional. Without one, `ember()` compiles templates
|
|
57
57
|
(to `precompileTemplate`), decorators (via
|
|
58
58
|
[decorator-transforms](https://github.com/ef4/decorator-transforms)), and
|
|
59
|
-
TypeScript; with one, your config runs instead.
|
|
59
|
+
TypeScript; with one, your config runs instead. A `babel.publish.config.*` is
|
|
60
|
+
preferred over a `babel.config.*` — see
|
|
61
|
+
[Publish vs. development babel config](#publish-vs-development-babel-config).
|
|
62
|
+
|
|
63
|
+
### Entrypoints
|
|
64
|
+
|
|
65
|
+
Entries may be any extension — `.ts`, `.js`, `.gts`, `.gjs`:
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
export default defineConfig({
|
|
69
|
+
entry: ["./src/index.ts", "./src/components/menu.gts"],
|
|
70
|
+
plugins: [ember()],
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
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.
|
|
60
81
|
|
|
61
82
|
### Declarations
|
|
62
83
|
|
|
63
84
|
Declarations are emitted with isolated declarations — the only declaration
|
|
64
85
|
pipeline that can see `<template>` (`.gts`/`.gjs`) modules, which exist only
|
|
65
|
-
inside the bundler's module graph.
|
|
86
|
+
inside the bundler's module graph. The tsconfig your build uses must enable it
|
|
66
87
|
(`ember()` errors otherwise):
|
|
67
88
|
|
|
68
89
|
```jsonc
|
|
@@ -82,6 +103,48 @@ import type { TOC } from "@ember/component/template-only";
|
|
|
82
103
|
export const Badge: TOC<BadgeSignature> = <template>...</template>;
|
|
83
104
|
```
|
|
84
105
|
|
|
106
|
+
#### Keeping the constraint off dev-only code
|
|
107
|
+
|
|
108
|
+
`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.
|
|
113
|
+
|
|
114
|
+
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
|
|
117
|
+
editors and `tsc --noEmit`:
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
// tsdown.config.js
|
|
121
|
+
export default defineConfig({
|
|
122
|
+
entry: ["./src/index.ts"],
|
|
123
|
+
// include: ["src/**/*"], isolatedDeclarations: true
|
|
124
|
+
tsconfig: "./tsconfig.publish.json",
|
|
125
|
+
plugins: [ember()],
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
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`.)
|
|
132
|
+
|
|
133
|
+
`tsconfig: false` is rejected while declarations are on: with no tsconfig there
|
|
134
|
+
is no `isolatedDeclarations`, and the fallback pipeline can't see compiled
|
|
135
|
+
`.gts`, so it would fail later with "Source file not found". Set `dts: false`
|
|
136
|
+
alongside it if the library ships no types.
|
|
137
|
+
|
|
138
|
+
A tsconfig's relative paths resolve against the file itself, so a config kept in
|
|
139
|
+
`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.
|
|
143
|
+
|
|
144
|
+
The tradeoff: isolated-declaration errors in `src` then surface when you build
|
|
145
|
+
rather than in your editor, since the editor uses `tsconfig.json`. Run the build
|
|
146
|
+
(or `tsc --noEmit -p tsconfig.publish.json`) in CI so nothing lands unchecked.
|
|
147
|
+
|
|
85
148
|
## CSS
|
|
86
149
|
|
|
87
150
|
Components that import co-located CSS (`import './popup.css'`) need
|
|
@@ -140,8 +203,9 @@ it.
|
|
|
140
203
|
|
|
141
204
|
`ember()` returns an array of rolldown plugins:
|
|
142
205
|
|
|
143
|
-
- **`emberIsolatedDeclarations()`** — errors when
|
|
144
|
-
|
|
206
|
+
- **`emberIsolatedDeclarations()`** — errors when the tsconfig the build uses
|
|
207
|
+
(tsdown's `tsconfig` option, defaulting to `tsconfig.json`) is present without
|
|
208
|
+
`isolatedDeclarations: true`.
|
|
145
209
|
- **`emberExternals()`** — keeps your `dependencies`, `peerDependencies`, and
|
|
146
210
|
the ember virtual packages (e.g. `@ember/component`, `@glimmer/tracking`, the
|
|
147
211
|
template compiler) external, so the consuming app resolves them.
|
|
@@ -151,7 +215,9 @@ it.
|
|
|
151
215
|
`.gts` specifiers in emitted `.d.ts` files.
|
|
152
216
|
- **`emberBabel()`** — runs babel with `babelHelpers: "bundled"`, but only on
|
|
153
217
|
the files that actually need it (template-tag, decorators, template imports);
|
|
154
|
-
everything else stays on rolldown's fast native (oxc) transform.
|
|
218
|
+
everything else stays on rolldown's fast native (oxc) transform. Uses your
|
|
219
|
+
`babel.publish.config.*` (root or `config/`) in preference to your
|
|
220
|
+
`babel.config.*`.
|
|
155
221
|
|
|
156
222
|
## Configuration
|
|
157
223
|
|
|
@@ -173,6 +239,46 @@ combined with a babel config file — a config lists
|
|
|
173
239
|
`babel-plugin-ember-template-compilation` itself, so its transforms belong
|
|
174
240
|
there.
|
|
175
241
|
|
|
242
|
+
### Publish vs. development babel config
|
|
243
|
+
|
|
244
|
+
A library's plain `babel.config.*` is usually its _development_ config: it
|
|
245
|
+
compiles `@embroider/macros` away, targets the wire format, and wires up
|
|
246
|
+
whatever the in-package demo app or test suite needs. None of that belongs in a
|
|
247
|
+
published artifact — macros must survive for the consuming app to evaluate, and
|
|
248
|
+
the wire format is private between one template compiler and one glimmer
|
|
249
|
+
runtime of the same version.
|
|
250
|
+
|
|
251
|
+
Babel's own resolution can't tell those apart, so `ember()` looks for a config
|
|
252
|
+
named for publishing first. With no explicit `configFile`, detection is:
|
|
253
|
+
|
|
254
|
+
1. `babel.publish.config.{mjs,cjs,js,mts,cts,ts,json}` in the package root
|
|
255
|
+
2. the same names in `config/`
|
|
256
|
+
3. otherwise babel's own resolution (`babel.config.*`, honoring `rootMode`)
|
|
257
|
+
4. otherwise no config file — templates, decorators and TypeScript are still
|
|
258
|
+
handled by the built-in defaults
|
|
259
|
+
|
|
260
|
+
So a library that keeps both configs needs no `babel` option at all:
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
my-addon/
|
|
264
|
+
babel.config.mjs # dev: macros compiled, wire format, test-app wiring
|
|
265
|
+
babel.publish.config.mjs # what ember() uses
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
...and so does one that keeps build configuration out of its root:
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
my-addon/
|
|
272
|
+
babel.config.mjs
|
|
273
|
+
config/
|
|
274
|
+
babel.publish.config.mjs # what ember() uses
|
|
275
|
+
tsconfig.publish.json # see below
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Set `configFile` explicitly to override that (`configFile: false` ignores config
|
|
279
|
+
files entirely, which is what you want when your publish config would just
|
|
280
|
+
restate the built-in defaults).
|
|
281
|
+
|
|
176
282
|
## App re-exports
|
|
177
283
|
|
|
178
284
|
For libraries whose modules must appear in the consuming app's namespace
|
|
@@ -194,12 +300,14 @@ export default defineConfig({
|
|
|
194
300
|
|
|
195
301
|
With no arguments, top-level services (`services/*`) are re-exported — under
|
|
196
302
|
strict mode, components and helpers are imported, but services are still
|
|
197
|
-
injected by name. A string
|
|
198
|
-
full control:
|
|
303
|
+
injected by name. A string or array of strings is the include glob(s),
|
|
304
|
+
optionally followed by the remaining options; an object gives full control:
|
|
199
305
|
|
|
200
306
|
```js
|
|
201
307
|
appReexports(); // services/*
|
|
202
308
|
appReexports("components/**"); // one include glob
|
|
309
|
+
appReexports(["services/*", "helpers/*"]);
|
|
310
|
+
appReexports("components/**", { exclude: ["components/-private/**"] });
|
|
203
311
|
appReexports({ include: ["services/*", "helpers/*"], exclude: [...] });
|
|
204
312
|
```
|
|
205
313
|
|
|
@@ -51,13 +51,16 @@ describe("appReexports", () => {
|
|
|
51
51
|
await rm(projectDir, { recursive: true, force: true });
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
async function build(
|
|
54
|
+
async function build(
|
|
55
|
+
include?: string | string[] | AppReexportsOptions,
|
|
56
|
+
options?: Omit<AppReexportsOptions, "include">,
|
|
57
|
+
) {
|
|
55
58
|
const bundle = await rolldown({
|
|
56
59
|
input: {
|
|
57
60
|
"components/foo": "./src/components/foo.js",
|
|
58
61
|
"utils/math": "./src/utils/math.js",
|
|
59
62
|
},
|
|
60
|
-
plugins: [appReexports(options)],
|
|
63
|
+
plugins: [appReexports(include, options)],
|
|
61
64
|
});
|
|
62
65
|
|
|
63
66
|
await bundle.write({ dir: "dist" });
|
|
@@ -100,6 +103,36 @@ describe("appReexports", () => {
|
|
|
100
103
|
expect(Object.keys(manifest["ember-addon"]["app-js"])).toEqual(["./components/foo.js"]);
|
|
101
104
|
});
|
|
102
105
|
|
|
106
|
+
it("accepts an array of include globs", async () => {
|
|
107
|
+
await build(["components/**", "utils/**"]);
|
|
108
|
+
|
|
109
|
+
const manifest = await readJson("package.json");
|
|
110
|
+
|
|
111
|
+
expect(Object.keys(manifest["ember-addon"]["app-js"])).toEqual([
|
|
112
|
+
"./components/foo.js",
|
|
113
|
+
"./utils/math.js",
|
|
114
|
+
]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("accepts options as the second argument alongside a string or array include", async () => {
|
|
118
|
+
await build("**/*.js", { exclude: ["utils/**"] });
|
|
119
|
+
|
|
120
|
+
let manifest = await readJson("package.json");
|
|
121
|
+
|
|
122
|
+
expect(Object.keys(manifest["ember-addon"]["app-js"])).toEqual(["./components/foo.js"]);
|
|
123
|
+
|
|
124
|
+
await build(["components/**", "utils/**"], {
|
|
125
|
+
mapFilename: (name) => name.replace("math", "arithmetic"),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
manifest = await readJson("package.json");
|
|
129
|
+
|
|
130
|
+
expect(manifest["ember-addon"]["app-js"]).toEqual({
|
|
131
|
+
"./components/foo.js": "./dist/_app_/components/foo.js",
|
|
132
|
+
"./utils/arithmetic.js": "./dist/_app_/utils/arithmetic.js",
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
103
136
|
it("defaults to top-level services", async () => {
|
|
104
137
|
await mkdir(join(projectDir, "src/services/nested"), { recursive: true });
|
|
105
138
|
await writeFile(join(projectDir, "src/services/session.js"), "export default class {}\n");
|
|
@@ -65,16 +65,27 @@ export interface AppReexportsOptions {
|
|
|
65
65
|
* ```
|
|
66
66
|
*
|
|
67
67
|
* With no arguments, top-level services (`services/*`) are re-exported. A
|
|
68
|
-
* string
|
|
68
|
+
* string or array of strings is the include glob(s), optionally followed by
|
|
69
|
+
* the remaining options; an object gives full control:
|
|
69
70
|
*
|
|
70
71
|
* ```js
|
|
71
72
|
* appReexports(); // services/*
|
|
72
73
|
* appReexports("components/**"); // one include glob
|
|
74
|
+
* appReexports(["services/*", "helpers/*"]);
|
|
75
|
+
* appReexports("components/**", { exclude: ["components/-private/**"] });
|
|
73
76
|
* appReexports({ include: ["services/*", "helpers/*"], exclude: [...] });
|
|
74
77
|
* ```
|
|
75
78
|
*/
|
|
76
|
-
export function appReexports(
|
|
77
|
-
|
|
79
|
+
export function appReexports(
|
|
80
|
+
includeOrOptions: string | string[] | AppReexportsOptions = {},
|
|
81
|
+
options: Omit<AppReexportsOptions, "include"> = {},
|
|
82
|
+
): Plugin {
|
|
83
|
+
const resolved =
|
|
84
|
+
typeof includeOrOptions === "string"
|
|
85
|
+
? { ...options, include: [includeOrOptions] }
|
|
86
|
+
: Array.isArray(includeOrOptions)
|
|
87
|
+
? { ...options, include: includeOrOptions }
|
|
88
|
+
: includeOrOptions;
|
|
78
89
|
const include = resolved.include ?? ["services/*"];
|
|
79
90
|
|
|
80
91
|
return {
|
|
@@ -1,6 +1,85 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdtemp, mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { rolldown } from "rolldown";
|
|
5
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
2
6
|
|
|
3
|
-
import { emberBabel } from "./babel.ts";
|
|
7
|
+
import { emberBabel, type BabelOptions } from "./babel.ts";
|
|
8
|
+
import { emberTransform } from "./transform.ts";
|
|
9
|
+
|
|
10
|
+
let restoreCwd: (() => void) | undefined;
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
restoreCwd?.();
|
|
14
|
+
restoreCwd = undefined;
|
|
15
|
+
});
|
|
16
|
+
|
|
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.
|
|
20
|
+
*/
|
|
21
|
+
function markerConfig(name: string): string {
|
|
22
|
+
return [
|
|
23
|
+
`export default {`,
|
|
24
|
+
` plugins: [`,
|
|
25
|
+
` function marker() {`,
|
|
26
|
+
` return {`,
|
|
27
|
+
` visitor: {`,
|
|
28
|
+
` StringLiteral(path) {`,
|
|
29
|
+
` if (path.node.value === 'MARKER') path.node.value = '${name}';`,
|
|
30
|
+
` },`,
|
|
31
|
+
` },`,
|
|
32
|
+
` };`,
|
|
33
|
+
` },`,
|
|
34
|
+
` ],`,
|
|
35
|
+
`};`,
|
|
36
|
+
].join("\n");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
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.
|
|
43
|
+
*/
|
|
44
|
+
async function bundleWithBabel(
|
|
45
|
+
files: Record<string, string>,
|
|
46
|
+
options?: BabelOptions,
|
|
47
|
+
): Promise<string> {
|
|
48
|
+
const dir = await mkdtemp(path.join(tmpdir(), "ember-rolldown-babel-"));
|
|
49
|
+
|
|
50
|
+
const withDefaults: Record<string, string> = {
|
|
51
|
+
"package.json": JSON.stringify({ name: "fixture", version: "0.0.0", type: "module" }),
|
|
52
|
+
"index.gts": [
|
|
53
|
+
`export const marker = 'MARKER';`,
|
|
54
|
+
`export default <template>hi</template>;`,
|
|
55
|
+
].join("\n"),
|
|
56
|
+
...files,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
for (const [relative, source] of Object.entries(withDefaults)) {
|
|
60
|
+
const full = path.join(dir, relative);
|
|
61
|
+
await mkdir(path.dirname(full), { recursive: true });
|
|
62
|
+
await writeFile(full, source, "utf8");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const previousCwd = process.cwd();
|
|
66
|
+
process.chdir(dir);
|
|
67
|
+
restoreCwd = () => process.chdir(previousCwd);
|
|
68
|
+
|
|
69
|
+
const build = await rolldown({
|
|
70
|
+
input: [path.join(dir, "index.gts")],
|
|
71
|
+
plugins: [emberTransform(), emberBabel(options)],
|
|
72
|
+
external: (id) => !(id.startsWith(".") || path.isAbsolute(id)),
|
|
73
|
+
onwarn() {},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const { output } = await build.generate({ format: "es" });
|
|
77
|
+
|
|
78
|
+
return output
|
|
79
|
+
.filter((chunk): chunk is typeof chunk & { code: string } => "code" in chunk)
|
|
80
|
+
.map((chunk) => chunk.code)
|
|
81
|
+
.join("\n");
|
|
82
|
+
}
|
|
4
83
|
|
|
5
84
|
describe("emberBabel", () => {
|
|
6
85
|
it("rejects templateTransforms combined with a config file", () => {
|
|
@@ -14,4 +93,48 @@ describe("emberBabel", () => {
|
|
|
14
93
|
emberBabel({ configFile: false, templateTransforms: ["some-transform"] }),
|
|
15
94
|
).not.toThrow();
|
|
16
95
|
});
|
|
96
|
+
|
|
97
|
+
describe("config detection", () => {
|
|
98
|
+
it("prefers babel.publish.config over babel.config", async () => {
|
|
99
|
+
const code = await bundleWithBabel({
|
|
100
|
+
"babel.config.mjs": markerConfig("dev"),
|
|
101
|
+
"babel.publish.config.mjs": markerConfig("publish"),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(code).toContain(`"publish"`);
|
|
105
|
+
expect(code).not.toContain(`"dev"`);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("falls back to babel.config when there is no publish config", async () => {
|
|
109
|
+
const code = await bundleWithBabel({ "babel.config.mjs": markerConfig("dev") });
|
|
110
|
+
|
|
111
|
+
expect(code).toContain(`"dev"`);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("uses an explicit configFile over either", async () => {
|
|
115
|
+
const code = await bundleWithBabel(
|
|
116
|
+
{
|
|
117
|
+
"babel.config.mjs": markerConfig("dev"),
|
|
118
|
+
"babel.publish.config.mjs": markerConfig("publish"),
|
|
119
|
+
"babel.explicit.config.mjs": markerConfig("explicit"),
|
|
120
|
+
},
|
|
121
|
+
{ configFile: "./babel.explicit.config.mjs" },
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
expect(code).toContain(`"explicit"`);
|
|
125
|
+
expect(code).not.toContain(`"publish"`);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("ignores every config file when configFile is false", async () => {
|
|
129
|
+
const code = await bundleWithBabel(
|
|
130
|
+
{
|
|
131
|
+
"babel.config.mjs": markerConfig("dev"),
|
|
132
|
+
"babel.publish.config.mjs": markerConfig("publish"),
|
|
133
|
+
},
|
|
134
|
+
{ configFile: false },
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
expect(code).toContain(`"MARKER"`);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
17
140
|
});
|
|
@@ -3,6 +3,8 @@ import transformTypeScript from "@babel/plugin-transform-typescript";
|
|
|
3
3
|
import templateCompilation from "babel-plugin-ember-template-compilation";
|
|
4
4
|
import decoratorTransforms from "decorator-transforms";
|
|
5
5
|
import { loadPartialConfigSync } from "@babel/core";
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
import { resolve } from "node:path";
|
|
6
8
|
import type { PluginItem } from "@babel/core";
|
|
7
9
|
import type { Transform } from "babel-plugin-ember-template-compilation";
|
|
8
10
|
import type { Plugin } from "rolldown";
|
|
@@ -12,8 +14,9 @@ export interface BabelOptions {
|
|
|
12
14
|
/**
|
|
13
15
|
* The babel config file to use.
|
|
14
16
|
*
|
|
15
|
-
* - `undefined`: auto-detect
|
|
16
|
-
* config
|
|
17
|
+
* - `undefined`: auto-detect. A `babel.publish.config.*` (in the package root
|
|
18
|
+
* or `config/`) wins, because this is a publish build; otherwise babel's own
|
|
19
|
+
* config resolution applies (see `detectConfigFile`).
|
|
17
20
|
* - a string: use that config file.
|
|
18
21
|
* - `false`: ignore config files entirely.
|
|
19
22
|
*
|
|
@@ -46,14 +49,55 @@ export interface BabelOptions {
|
|
|
46
49
|
};
|
|
47
50
|
}
|
|
48
51
|
|
|
52
|
+
/** Tried in order, so an ESM config wins over a JSON one of the same name. */
|
|
53
|
+
const PUBLISH_CONFIG_EXTENSIONS = ["mjs", "cjs", "js", "mts", "cts", "ts", "json"];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Tried in order: next to package.json first, then `config/` for libraries that
|
|
57
|
+
* keep their build configuration out of the package root.
|
|
58
|
+
*/
|
|
59
|
+
const PUBLISH_CONFIG_DIRECTORIES = [".", "config"];
|
|
60
|
+
|
|
49
61
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
62
|
+
* A `babel.publish.config.*` (in the package root or `config/`), if there is
|
|
63
|
+
* one.
|
|
64
|
+
*
|
|
65
|
+
* A library's plain `babel.config.*` is its *development* config: it typically
|
|
66
|
+
* compiles `@embroider/macros` away, targets the wire format, and wires up
|
|
67
|
+
* whatever the in-package demo app or test suite needs. None of that belongs in
|
|
68
|
+
* a published artifact -- macros must stay for the consuming app to evaluate,
|
|
69
|
+
* and the wire format is private between one template compiler and one glimmer
|
|
70
|
+
* runtime. Babel's own resolution can't tell the two apart (it finds whatever
|
|
71
|
+
* `babel.config.*` exists), so a publish build has to prefer the config named
|
|
72
|
+
* for publishing.
|
|
73
|
+
*/
|
|
74
|
+
function detectPublishConfigFile(): string | undefined {
|
|
75
|
+
for (const directory of PUBLISH_CONFIG_DIRECTORIES) {
|
|
76
|
+
for (const extension of PUBLISH_CONFIG_EXTENSIONS) {
|
|
77
|
+
const candidate = resolve(directory, `babel.publish.config.${extension}`);
|
|
78
|
+
|
|
79
|
+
if (existsSync(candidate)) return candidate;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The config this publish build should use: a `babel.publish.config.*` when the
|
|
88
|
+
* library has one, else the project's root babel config resolved the way babel
|
|
89
|
+
* itself does (`babel.config.{js,cjs,mjs,cts,json}`, honoring `rootMode`
|
|
90
|
+
* semantics) -- which for a library whose only config *is* its publish config
|
|
91
|
+
* is the same file either way.
|
|
52
92
|
*
|
|
53
93
|
* The sync API evaluates the config file; on node 24+ that works for native
|
|
54
94
|
* ESM configs too (require(esm)).
|
|
55
95
|
*/
|
|
56
96
|
function detectConfigFile(): string | undefined {
|
|
97
|
+
const publishConfig = detectPublishConfigFile();
|
|
98
|
+
|
|
99
|
+
if (publishConfig) return publishConfig;
|
|
100
|
+
|
|
57
101
|
const partial = loadPartialConfigSync({ cwd: process.cwd() });
|
|
58
102
|
|
|
59
103
|
return partial?.config ?? undefined;
|
|
@@ -112,9 +156,10 @@ function defaultPlugins(templateTransforms?: Transform[]): PluginItem[] {
|
|
|
112
156
|
* to only the files that actually need it (template-tag, decorators, template
|
|
113
157
|
* imports). Everything else stays on the native transform.
|
|
114
158
|
*
|
|
115
|
-
* The library's own babel config is used when it exists
|
|
116
|
-
* `
|
|
117
|
-
* config
|
|
159
|
+
* The library's own babel config is used when it exists -- its
|
|
160
|
+
* `babel.publish.config.*` (root or `config/`) in preference to its
|
|
161
|
+
* `babel.config.*`, since this is a publish build. Without one, `defaultPlugins` covers templates, decorators,
|
|
162
|
+
* and TypeScript, so no config file is required.
|
|
118
163
|
*
|
|
119
164
|
* Libraries default to `babelHelpers: "bundled"` so the emitted output is
|
|
120
165
|
* self-contained.
|