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
@@ -5,16 +5,16 @@ import type { Plugin } from "rolldown";
5
5
  import type { UserConfig } from "tsdown";
6
6
 
7
7
  /**
8
- * Resolve the tsconfig the build will actually use, mirroring tsdown's own
9
- * `tsconfig` option (`string | boolean`):
8
+ * Resolve the tsconfig the build will use.
10
9
  *
11
- * - `undefined` / `true`: `tsconfig.json` in the cwd.
12
- * - a path to a file: that file.
13
- * - a path to a directory: `tsconfig.json` inside it.
14
- * - `false`: no tsconfig at all.
10
+ * Mirrors tsdown's own `tsconfig` option (`string | boolean`):
11
+ * - `undefined` / `true`: `tsconfig.json` in the cwd
12
+ * - a path to a file: that file
13
+ * - a path to a directory: `tsconfig.json` inside it
14
+ * - `false`: no tsconfig at all
15
15
  *
16
- * Under plain rolldown there is no such option, so the cwd's `tsconfig.json`
17
- * is all there is.
16
+ * Under plain rolldown there is no such option,
17
+ * so the cwd's `tsconfig.json` is all there is.
18
18
  */
19
19
  function resolveTsconfigPath(option: string | boolean | undefined): string | undefined {
20
20
  if (option === false) return undefined;
@@ -33,33 +33,41 @@ function resolveTsconfigPath(option: string | boolean | undefined): string | und
33
33
  }
34
34
 
35
35
  /**
36
- * Errors the build when the tsconfig it builds with does not set
37
- * `isolatedDeclarations: true`.
36
+ * Errors the build when the tsconfig it builds with
37
+ * does not set `isolatedDeclarations: true`.
38
38
  *
39
- * This is required: declarations for `.gts`/`.gjs` (template tag) modules can
40
- * only be emitted by the isolated-declarations pipeline, which reads compiled
41
- * modules from the bundler's module graph. The `tsc`-based pipeline reads
42
- * source files from disk, where the compiled modules don't exist, and fails
43
- * with "Source file not found".
39
+ * This is required.
40
+ * Declarations for `.gts` / `.gjs` (template tag) modules can only be emitted
41
+ * by the isolated-declarations pipeline,
42
+ * which reads compiled modules from the bundler's module graph.
44
43
  *
45
- * Failing fast here -- rather than only when declaration emit happens to run
46
- * -- keeps the project honest: editors and `tsc`/`ember-tsc` then check the
47
- * same rules the build enforces (exported values need explicit type
48
- * annotations, e.g. `export const X: TOC<Sig> = <template>...`).
44
+ * The `tsc`-based pipeline reads source files from disk.
45
+ * The compiled modules don't exist there, so it fails with "Source file not found".
49
46
  *
50
- * The tsconfig checked is the one tsdown's `tsconfig` option points at, not
51
- * necessarily `tsconfig.json`. A library whose package also holds dev-only code
52
- * (a demo app, in-package tests) can therefore keep a permissive
53
- * `tsconfig.json` covering everything for editors and `tsc --noEmit`, and point
54
- * the build at a `tsconfig.publish.json` that covers only the publishable
55
- * sources -- isolated declarations then constrain exactly the code that gets
56
- * declarations emitted for it, and nothing else.
47
+ * Failing fast here, rather than only when declaration emit happens to run,
48
+ * keeps the project honest.
49
+ * Editors and `tsc` / `ember-tsc` then check the same rules the build enforces:
50
+ * exported values need explicit type annotations,
51
+ * e.g. `export const X: TOC<Sig> = <template>...`
57
52
  *
58
- * Projects with no tsconfig (JavaScript libraries) or without the `typescript`
59
- * package have no declarations to emit, so there is nothing to check.
60
- * `tsconfig: false` is different: it is an explicit opt-out, and combined with
61
- * declaration emit it produces a confusing "Source file not found" from the
62
- * tsc-based pipeline, so it errors here instead.
53
+ * The tsconfig checked is the one tsdown's `tsconfig` option points at,
54
+ * not necessarily `tsconfig.json`.
55
+ *
56
+ * So a library whose package also holds dev-only code (a demo app, in-package tests) can:
57
+ * - keep a permissive `tsconfig.json` covering everything,
58
+ * for editors and `tsc --noEmit`
59
+ * - point the build at a `tsconfig.publish.json` that covers only the publishable sources
60
+ *
61
+ * Isolated declarations then constrain exactly the code that gets declarations emitted,
62
+ * and nothing else.
63
+ *
64
+ * Projects with no tsconfig (JavaScript libraries) or without the `typescript` package
65
+ * have no declarations to emit, so there is nothing to check.
66
+ *
67
+ * `tsconfig: false` is different.
68
+ * It is an explicit opt-out.
69
+ * Combined with declaration emit, it produces a confusing "Source file not found"
70
+ * from the tsc-based pipeline, so it errors here instead.
63
71
  */
64
72
  export function emberIsolatedDeclarations(): Plugin {
65
73
  let tsconfigOption: string | boolean | undefined;
@@ -68,13 +76,15 @@ export function emberIsolatedDeclarations(): Plugin {
68
76
  return {
69
77
  name: "ember:isolated-declarations",
70
78
 
71
- // Not called under plain rolldown, which leaves `tsconfigOption` undefined
72
- // -- i.e. the cwd's tsconfig.json, the only thing it could mean there.
79
+ // Not called under plain rolldown, which leaves `tsconfigOption` undefined.
80
+ // That means the cwd's tsconfig.json, the only thing it could mean there.
73
81
  tsdownConfig(config: UserConfig) {
74
82
  tsconfigOption = config.tsconfig;
75
- // `emberConfig()` turns dts on unless the library turned it off, so an
76
- // explicit `false` is the only "no declarations" signal. Hook order
77
- // between plugins isn't guaranteed, so don't rely on it having run.
83
+ // `emberConfig()` turns dts on unless the library turned it off,
84
+ // so an explicit `false` is the only "no declarations" signal.
85
+ //
86
+ // Hook order between plugins isn't guaranteed,
87
+ // so don't rely on it having run.
78
88
  emitsDeclarations = config.dts !== false;
79
89
  },
80
90
 
@@ -82,11 +92,14 @@ export function emberIsolatedDeclarations(): Plugin {
82
92
  if (tsconfigOption === false) {
83
93
  if (!emitsDeclarations) return;
84
94
 
85
- // Without a tsconfig there is no `isolatedDeclarations`, so tsdown
86
- // falls back to the tsc-based declaration pipeline -- which reads
87
- // source files from disk and dies with "Source file not found" on
88
- // every `.gts`/`.gjs`, since those only exist compiled in the module
89
- // graph. Say so here instead of leaving that to be decoded.
95
+ // Without a tsconfig there is no `isolatedDeclarations`,
96
+ // so tsdown falls back to the tsc-based declaration pipeline.
97
+ //
98
+ // That pipeline reads source files from disk,
99
+ // and dies with "Source file not found" on every `.gts` / `.gjs`,
100
+ // since those only exist compiled in the module graph.
101
+ //
102
+ // Say so here instead of leaving that to be decoded.
90
103
  this.error(
91
104
  `\`tsconfig: false\` cannot be combined with declaration emit.\n\n` +
92
105
  `Declarations are emitted by the isolated-declarations pipeline, which needs a ` +
@@ -112,8 +125,8 @@ export function emberIsolatedDeclarations(): Plugin {
112
125
  const { config, error } = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
113
126
 
114
127
  if (error) {
115
- // Unreadable/unparsable tsconfig: tsc itself will report this with a
116
- // better message than we can.
128
+ // Unreadable / unparsable tsconfig.
129
+ // tsc itself will report this with a better message than we can.
117
130
  return;
118
131
  }
119
132
 
@@ -1,22 +1,25 @@
1
1
  /**
2
- * What this package hands back: an object that rolldown and tsdown accept in
3
- * `plugins`.
2
+ * What this package hands back:
3
+ * an object that rolldown and tsdown accept in `plugins`.
4
4
  *
5
- * Structurally this *is* rolldown's `Plugin` -- `name` is its only required
6
- * member, every hook is optional -- so the two are assignable in both
7
- * directions: a consumer who imports rolldown's own type can still write
8
- * `const plugins: Plugin[] = ember()`, and tsdown's `plugins` option lists
9
- * `{ name: string }` among the shapes it takes.
5
+ * Structurally this *is* rolldown's `Plugin`.
6
+ * `name` is its only required member, every hook is optional.
10
7
  *
11
- * Declaring it here rather than re-exporting rolldown's type is what keeps
12
- * `rolldown` out of the published declarations. Otherwise it has to become a
13
- * runtime `dependency` purely so consumers can resolve a type: leave it a
14
- * devDependency and the declaration bundler inlines a ~200kB private copy of
15
- * rolldown's types instead, nominally distinct from the consumer's own.
8
+ * So the two are assignable in both directions:
9
+ * - a consumer who imports rolldown's own type can still write
10
+ * `const plugins: Plugin[] = ember()`
11
+ * - tsdown's `plugins` option lists `{ name: string }` among the shapes it takes
16
12
  *
17
- * The precise hook signatures still apply inside this package -- each plugin
18
- * function is typed with rolldown's real `Plugin` (a devDependency), and this
19
- * type only widens what crosses the package boundary.
13
+ * Declaring it here, rather than re-exporting rolldown's type,
14
+ * keeps `rolldown` out of the published declarations.
15
+ *
16
+ * Otherwise it has to become a runtime `dependency`, purely so consumers can resolve a type.
17
+ * Leave it a devDependency, and the declaration bundler inlines a ~200kB private copy
18
+ * of rolldown's types instead, nominally distinct from the consumer's own.
19
+ *
20
+ * The precise hook signatures still apply inside this package.
21
+ * Each plugin function is typed with rolldown's real `Plugin` (a devDependency).
22
+ * This type only widens what crosses the package boundary.
20
23
  */
21
24
  export interface RolldownPluginLike {
22
25
  name: string;
@@ -5,10 +5,13 @@ import { fileURLToPath } from "node:url";
5
5
  import { beforeAll, describe, expect, it } from "vitest";
6
6
 
7
7
  /**
8
- * This package's own published shape: consumers write their build config in
9
- * TypeScript, so `import { ember } from "@nullvoxpopuli/ember-rolldown"` has to
10
- * carry types. Without them it is an implicit `any` (TS7016), and a config file
11
- * silently stops being type-checked.
8
+ * This package's own published shape.
9
+ *
10
+ * Consumers write their build config in TypeScript,
11
+ * so `import { ember } from "@nullvoxpopuli/ember-rolldown"` has to carry types.
12
+ *
13
+ * Without them it is an implicit `any` (TS7016),
14
+ * and a config file silently stops being type-checked.
12
15
  */
13
16
  const packageDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
14
17
 
@@ -43,8 +46,8 @@ async function declarations() {
43
46
 
44
47
  describe("published types", () => {
45
48
  beforeAll(async () => {
46
- // `clean` is on, but don't let these assertions depend on that: stale
47
- // artifacts from an earlier build satisfy every one of them on their own
49
+ // `clean` is on, but don't let these assertions depend on that.
50
+ // Stale artifacts from an earlier build satisfy every one of them on their own.
48
51
  await rm(path.join(packageDir, "dist"), { recursive: true, force: true });
49
52
  await execa("pnpm", ["build"], { cwd: packageDir });
50
53
  }, 120_000);
@@ -66,14 +69,16 @@ describe("published types", () => {
66
69
 
67
70
  /**
68
71
  * A declaration may only reach for packages a consumer is guaranteed to have.
69
- * Reference a devDependency and there are two ways to lose, both quiet:
70
- * the specifier survives and consumers can't resolve it (`skipLibCheck` hides
71
- * that, degrading the type to an error type), or the declaration bundler
72
- * inlines the whole type surface — a ~200kB copy of rolldown's types, in the
73
- * case that prompted this test, nominally distinct from the consumer's own.
74
72
  *
75
- * `ember()` returns plugins, so `RolldownPluginLike` exists to keep rolldown
76
- * on the devDependency side of that line.
73
+ * Reference a devDependency, and there are two ways to lose, both quiet:
74
+ * - the specifier survives and consumers can't resolve it
75
+ * (`skipLibCheck` hides that, degrading the type to an error type)
76
+ * - the declaration bundler inlines the whole type surface
77
+ * (a ~200kB copy of rolldown's types, in the case that prompted this test,
78
+ * nominally distinct from the consumer's own)
79
+ *
80
+ * `ember()` returns plugins,
81
+ * so `RolldownPluginLike` exists to keep rolldown on the devDependency side of that line.
77
82
  */
78
83
  it("declarations only reference packages consumers have", async () => {
79
84
  const manifest = await readManifest();
@@ -8,10 +8,15 @@ import { describe, expect, it } from "vitest";
8
8
  import { emberTransform } from "./transform.ts";
9
9
 
10
10
  /**
11
- * Drives the plugin through a real rolldown build. `files` is a map of
12
- * relative path -> source; the build entry is `index.ts`. Every bare (package)
13
- * specifier is marked external so the build only exercises the local
14
- * `.gts`/`.gjs`/`.ts` handling and doesn't need real dependencies installed.
11
+ * Drives the plugin through a real rolldown build.
12
+ *
13
+ * `files` is a map of relative path -> source.
14
+ * The build entry is `index.ts`.
15
+ *
16
+ * Every bare (package) specifier is marked external,
17
+ * so the build only exercises the local `.gts` / `.gjs` / `.ts` handling
18
+ * and doesn't need real dependencies installed.
19
+ *
15
20
  * Returns the concatenated code of every emitted chunk.
16
21
  */
17
22
  async function bundle(
@@ -45,8 +50,8 @@ async function bundle(
45
50
  .map((chunk) => chunk.code)
46
51
  .join("\n");
47
52
 
48
- // rolldown's `//#region <path>` comments reference the random temp dir;
49
- // collapse them to the bare filename so snapshots stay deterministic.
53
+ // rolldown's `//#region <path>` comments reference the random temp dir.
54
+ // Collapse them to the bare filename so snapshots stay deterministic.
50
55
  return code.replace(/(\/\/#region ).*\/([^/\n]+)$/gm, "$1$2");
51
56
  }
52
57
 
@@ -180,11 +185,13 @@ describe("emberTransform (full plugin via rolldown)", () => {
180
185
 
181
186
  it("resolves an absolute .gts specifier imported from a plugin's virtual module", async () => {
182
187
  /**
183
- * The shape a code-generating plugin emits: a virtual module (id prefixed
184
- * with `\0`, so it exists nowhere on disk) whose generated source imports
185
- * real files by absolute path, because it has no directory to be relative
186
- * to. `path.dirname` of such an id is `"\0."`, so the specifier must not be
187
- * resolved against the importer's directory.
188
+ * The shape a code-generating plugin emits:
189
+ * a virtual module (id prefixed with `\0`, so it exists nowhere on disk)
190
+ * whose generated source imports real files by absolute path,
191
+ * because it has no directory to be relative to.
192
+ *
193
+ * `path.dirname` of such an id is `"\0."`,
194
+ * so the specifier must not be resolved against the importer's directory.
188
195
  */
189
196
  const registry = (dir: string): Plugin[] => {
190
197
  const source = "./registry?virtual=views";
@@ -225,10 +232,12 @@ describe("emberTransform (full plugin via rolldown)", () => {
225
232
  it("compiles a .gts module used directly as an entry (no importer)", async () => {
226
233
  const code = await bundle(
227
234
  {
228
- // Both an entry .gts and a .ts entry importing it: the shared module
229
- // must resolve to ONE id (the entry resolution realpaths, matching
230
- // rolldown's own resolver) so it lands in the entry chunk and the
231
- // index chunk imports it, rather than duplicating the code.
235
+ // Both an entry .gts and a .ts entry importing it.
236
+ //
237
+ // The shared module must resolve to ONE id
238
+ // (the entry resolution realpaths, matching rolldown's own resolver),
239
+ // so it lands in the entry chunk and the index chunk imports it,
240
+ // rather than duplicating the code.
232
241
  "index.ts": `export { default as Foo } from './foo.gts';`,
233
242
  "foo.gts": [
234
243
  `import Component from '@glimmer/component';`,
@@ -299,8 +308,8 @@ describe("emberTransform (full plugin via rolldown)", () => {
299
308
 
300
309
  const chunk = output.find((entry) => entry.type === "chunk" && "map" in entry);
301
310
  expect(chunk).toBeDefined();
302
- // The composed map traces through the rewrite back to the original .gts
303
- // source (content-tag's map survives the specifier-rewrite pass).
311
+ // The composed map traces through the rewrite back to the original .gts source.
312
+ // (content-tag's map survives the specifier-rewrite pass)
304
313
  const sources = (chunk as { map?: { sources: string[] } }).map?.sources ?? [];
305
314
  expect(sources.some((source) => source.endsWith("foo.gts"))).toBe(true);
306
315
  });
@@ -7,9 +7,10 @@ import type { Plugin } from "rolldown";
7
7
  const processor = new Preprocessor();
8
8
 
9
9
  /**
10
- * `.d.ts` files emitted for `.gts` source reference the original `.gts`
11
- * specifiers. Consumers can't resolve those, so we rewrite them back to
12
- * extension-less specifiers.
10
+ * `.d.ts` files emitted for `.gts` source reference the original `.gts` specifiers.
11
+ *
12
+ * Consumers can't resolve those,
13
+ * so we rewrite them back to extension-less specifiers.
13
14
  */
14
15
  function fixDeclarationImports(content: string): string {
15
16
  return content
@@ -41,10 +42,12 @@ async function fixDtsExtensionsInDir(dir: string): Promise<void> {
41
42
  /**
42
43
  * A sourcemap mapping every generated line to the same line of the input.
43
44
  *
44
- * `AAAA` is the [0, 0, 0, 0] VLQ segment (first line maps to source 0, line 0,
45
- * column 0); `AACA` is [0, 0, +1, 0] (each following line advances the source
46
- * line by one). Exact at line granularity, which is all the specifier rewrite
47
- * can disturb.
45
+ * - `AAAA` is the [0, 0, 0, 0] VLQ segment
46
+ * (first line maps to source 0, line 0, column 0)
47
+ * - `AACA` is [0, 0, +1, 0]
48
+ * (each following line advances the source line by one)
49
+ *
50
+ * Exact at line granularity, which is all the specifier rewrite can disturb.
48
51
  */
49
52
  function lineIdentityMap(id: string, source: string) {
50
53
  return {
@@ -60,8 +63,10 @@ function lineIdentityMap(id: string, source: string) {
60
63
  }
61
64
 
62
65
  /**
63
- * Maps `.gts` -> `.ts` and `.gjs` -> `.js` (so rolldown can identify them as
64
- * ts/js) and preprocesses `<template>` via content-tag.
66
+ * Preprocesses `<template>` via content-tag, and maps
67
+ * `.gts` → `.ts`
68
+ * `.gjs` → `.js`
69
+ * so rolldown can identify them as ts / js.
65
70
  */
66
71
  export function emberTransform(): Plugin {
67
72
  return {
@@ -70,21 +75,26 @@ export function emberTransform(): Plugin {
70
75
  resolveId: {
71
76
  order: "pre",
72
77
  handler(id, importer) {
73
- // Entries have no importer: their ids come straight from the build
74
- // config (tsdown expands entry globs to on-disk paths). Resolve a
75
- // `.gts`/`.gjs` entry to the same virtual `.ts`/`.js` id an imported
76
- // module gets, so the load hook compiles it via content-tag instead
77
- // of the raw `<template>` source hitting the parser. Entries may be
78
- // any extension — the emitted `.js`/`.d.ts` paths mirror the entry
79
- // paths either way (that's how tsdown's dts support works).
78
+ // Entries have no importer.
79
+ // Their ids come straight from the build config
80
+ // (tsdown expands entry globs to on-disk paths).
81
+ //
82
+ // Resolve a `.gts` / `.gjs` entry to the same virtual `.ts` / `.js` id
83
+ // an imported module gets, so the load hook compiles it via content-tag
84
+ // instead of the raw `<template>` source hitting the parser.
85
+ //
86
+ // Entries may be any extension.
87
+ // The emitted `.js` / `.d.ts` paths mirror the entry paths either way
88
+ // (that's how tsdown's dts support works).
80
89
  if (!importer) {
81
90
  if (!id.endsWith(".gts") && !id.endsWith(".gjs")) return null;
82
91
  if (!existsSync(id)) return null;
83
92
 
84
- // rolldown's default resolver realpaths entry ids (e.g. macOS
85
- // /var -> /private/var). Match it, or this same file imported from
86
- // another entry resolves to a second module id and gets duplicated
87
- // into both chunks.
93
+ // rolldown's default resolver realpaths entry ids
94
+ // (e.g. macOS /var -> /private/var).
95
+ //
96
+ // Match it, or this same file imported from another entry
97
+ // resolves to a second module id and gets duplicated into both chunks.
88
98
  const fileName = realpathSync(path.resolve(id));
89
99
 
90
100
  return {
@@ -93,13 +103,16 @@ export function emberTransform(): Plugin {
93
103
  };
94
104
  }
95
105
 
96
- // An absolute specifier already names the file, so it needs no
97
- // importer-relative resolution — and joining it onto the importer's
98
- // directory would corrupt it. That is not hypothetical: a plugin's
99
- // virtual module (id prefixed with `\0`) can generate imports of
100
- // on-disk files by absolute path, and `path.dirname("\0./registry")`
101
- // is `"\0."`, so joining yields `"\0./Users/.../thing.gts"` — a path
102
- // no `existsSync`/`readFile` can accept (it has a null byte in it).
106
+ // An absolute specifier already names the file,
107
+ // so it needs no importer-relative resolution.
108
+ // Joining it onto the importer's directory would corrupt it.
109
+ //
110
+ // That is not hypothetical.
111
+ // A plugin's virtual module (id prefixed with `\0`)
112
+ // can generate imports of on-disk files by absolute path,
113
+ // and `path.dirname("\0./registry")` is `"\0."`.
114
+ // Joining then yields `"\0./Users/.../thing.gts"`:
115
+ // a path no `existsSync` / `readFile` can accept (it has a null byte in it).
103
116
  const fileName = path.isAbsolute(id) ? id : path.join(path.dirname(importer), id);
104
117
 
105
118
  if (id.endsWith(".gts")) {
@@ -116,15 +129,19 @@ export function emberTransform(): Plugin {
116
129
  };
117
130
  }
118
131
 
119
- // A `.ts`/`.js` specifier whose only backing file is a `.gts`/`.gjs`
120
- // resolves to the virtual module. This must also serve `.d.ts`
121
- // importers: rolldown-plugin-dts resolves a declaration module's
122
- // imports through the plugin pipeline (`this.resolve`), and — when the
123
- // resolution is a source file — loads it (registering its declaration)
124
- // before mapping the import to the declaration id. Virtual modules
125
- // exist nowhere else, so if we don't answer here the import either
126
- // fails to resolve (no file on disk) or, worse, resolves to a
127
- // declaration id that was never registered.
132
+ // A `.ts` / `.js` specifier whose only backing file is a `.gts` / `.gjs`
133
+ // resolves to the virtual module.
134
+ //
135
+ // This must also serve `.d.ts` importers.
136
+ // rolldown-plugin-dts resolves a declaration module's imports
137
+ // through the plugin pipeline (`this.resolve`).
138
+ // When the resolution is a source file, it loads it (registering its declaration)
139
+ // before mapping the import to the declaration id.
140
+ //
141
+ // Virtual modules exist nowhere else.
142
+ // If we don't answer here, the import either:
143
+ // - fails to resolve (no file on disk)
144
+ // - or, worse, resolves to a declaration id that was never registered
128
145
  if (id.endsWith(".ts")) {
129
146
  const gtsFileName = fileName.replace(/\.ts$/, ".gts");
130
147
  if (existsSync(gtsFileName) && !existsSync(fileName)) {
@@ -152,9 +169,11 @@ export function emberTransform(): Plugin {
152
169
  const meta = this.getModuleInfo(id)?.meta ?? {};
153
170
  let fileName = meta?.fileName;
154
171
 
155
- // A virtual id can be loaded without having passed through our
156
- // resolveId (rolldown-plugin-dts calls `this.load({ id })` with just
157
- // the id), so no meta is attached: recover the backing file from disk.
172
+ // A virtual id can be loaded without having passed through our resolveId
173
+ // (rolldown-plugin-dts calls `this.load({ id })` with just the id),
174
+ // so no meta is attached.
175
+ //
176
+ // Recover the backing file from disk.
158
177
  if (!fileName && !existsSync(id)) {
159
178
  if (id.endsWith(".ts") && existsSync(id.replace(/\.ts$/, ".gts"))) {
160
179
  fileName = id.replace(/\.ts$/, ".gts");
@@ -188,12 +207,13 @@ export function emberTransform(): Plugin {
188
207
  id: /\.(js|ts)$/,
189
208
  },
190
209
  handler(input, id) {
191
- // Rewrite `.gts` specifiers to `.ts` — the (virtual) source id — in
192
- // runtime modules AND declaration modules alike. Declaration modules
193
- // must NOT be rewritten to `.d.ts`: rolldown-plugin-dts's resolver
194
- // treats a source-file resolution as "load it (registering its
195
- // declaration), then map to the declaration id", while a `.d.ts` id is
196
- // returned as-is — unloadable when the module isn't registered yet
210
+ // Rewrite `.gts` specifiers to `.ts`, the (virtual) source id,
211
+ // in runtime modules AND declaration modules alike.
212
+ //
213
+ // Declaration modules must NOT be rewritten to `.d.ts`.
214
+ // rolldown-plugin-dts's resolver treats a source-file resolution as
215
+ // "load it (registering its declaration), then map to the declaration id".
216
+ // A `.d.ts` id is returned as-is: unloadable when the module isn't registered yet
197
217
  // (e.g. the `.gts` is only ever type-imported).
198
218
  const output = input.replace(
199
219
  /(['"`])((?:\.\.?\/|\/|@|[A-Za-z0-9_\-])[^'"]*?\.gts)\1/g,
@@ -204,9 +224,9 @@ export function emberTransform(): Plugin {
204
224
  return null;
205
225
  }
206
226
 
207
- // The rewrite only shortens import specifiers in place, so a
208
- // line-identity map is accurate to the line (and to the column for
209
- // everything before the first rewritten specifier on a line).
227
+ // The rewrite only shortens import specifiers in place,
228
+ // so a line-identity map is accurate to the line
229
+ // (and to the column, for everything before the first rewritten specifier on a line).
210
230
  return { code: output, map: lineIdentityMap(id, input) };
211
231
  },
212
232
  },
@@ -8,30 +8,29 @@ A speed-optimized default meta-config for ember projects.
8
8
  npm add @nullvoxpopuli/ember-vite
9
9
  ```
10
10
 
11
- Requires node 24+, and — since these packages ship TypeScript source — a
12
- modern TypeScript when type-checking: 6+ with `lib` covering `es2025` (e.g.
13
- `esnext`).
11
+ Requires node 24+. These packages ship TypeScript source, so type-checking
12
+ needs TypeScript 6+ with `lib` covering `es2025` (for example `esnext`).
14
13
 
15
14
  ## Usage
16
15
 
17
- in your vite config
16
+ In your vite config:
18
17
 
19
18
  ```js
20
19
  import { defineConfig } from "vite";
21
20
  import { ember } from "@nullvoxpopuli/ember-vite";
22
21
 
23
22
  export default defineConfig({
24
- plugins: [
25
- ember(),
26
- ],
27
- }
23
+ plugins: [ember()],
24
+ });
28
25
  ```
29
26
 
30
- And remove any plugins from embroider or babel.
31
- Then delete from your package.json: `@rollup/plugin-babel`, `@embroider/core`, `@embroider/vite`.
27
+ Then:
28
+
29
+ 1. Remove any plugins from embroider or babel.
30
+ 2. Delete from your package.json: `@rollup/plugin-babel`, `@embroider/core`, `@embroider/vite`.
32
31
 
33
32
  > [!NOTE]
34
- > babel related deps are still needed for linting, so don't delete those
33
+ > Linting still needs the babel related deps, so keep those.
35
34
 
36
35
  ## Requirements
37
36
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nullvoxpopuli/ember-vite",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Meta plugin for ember projects focusing on speed.",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "packageManager": "pnpm@10.28.0",
27
27
  "dependencies": {
28
- "@babel/core": "^7.28.10",
28
+ "@babel/core": "^7.28.10 || ^8.0.0",
29
29
  "@embroider/core": "^4.4.2",
30
30
  "@embroider/macros": "^1.20.3",
31
31
  "@embroider/vite": "^1.5.0",