ember.nvp 0.5.0 → 0.7.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 (91) hide show
  1. package/.github/workflows/ci.yml +17 -2
  2. package/.release-plan.json +84 -4
  3. package/CHANGELOG.md +68 -0
  4. package/README.md +29 -0
  5. package/package.json +4 -2
  6. package/packages/build-tooling-utils/README.md +37 -0
  7. package/packages/build-tooling-utils/index.ts +2 -0
  8. package/packages/build-tooling-utils/package.json +42 -0
  9. package/packages/build-tooling-utils/rolldown.config.js +18 -0
  10. package/packages/build-tooling-utils/src/extensions.ts +18 -0
  11. package/packages/build-tooling-utils/src/maybe-babel.ts +146 -0
  12. package/packages/rolldown/README.md +115 -0
  13. package/packages/rolldown/index.ts +56 -0
  14. package/packages/rolldown/package.json +59 -0
  15. package/packages/rolldown/rolldown.config.js +21 -0
  16. package/packages/rolldown/src/babel.ts +136 -0
  17. package/packages/rolldown/src/define-config.ts +32 -0
  18. package/packages/rolldown/src/externals.ts +83 -0
  19. package/packages/rolldown/src/isolated-declarations.ts +65 -0
  20. package/packages/rolldown/src/transform.ts +162 -0
  21. package/packages/vite/README.md +7 -21
  22. package/packages/vite/index.ts +1 -1
  23. package/packages/vite/package.json +4 -6
  24. package/packages/vite/src/ember.ts +129 -15
  25. package/packages/vite/src/test-html.ts +86 -0
  26. package/src/bases/minimal-app/files/app/router.ts +1 -0
  27. package/src/bases/minimal-app/files/tsconfig.json +1 -0
  28. package/src/bases/minimal-app/index.js +2 -2
  29. package/src/bases/minimal-library/files/README.md +41 -0
  30. package/src/bases/minimal-library/files/package.json +49 -0
  31. package/src/bases/minimal-library/files/tsconfig.json +10 -0
  32. package/src/bases/minimal-library/files/tsdown.config.js +6 -0
  33. package/src/bases/minimal-library/index.js +158 -2
  34. package/src/cli/args.js +7 -1
  35. package/src/cli/index.js +90 -16
  36. package/src/cli/questions/layers.js +5 -2
  37. package/src/cli/questions/project-type.js +0 -5
  38. package/src/cli/questions/write.js +169 -0
  39. package/src/index.js +1 -0
  40. package/src/layers/are-the-types-wrong/index.js +74 -0
  41. package/src/layers/git/files/gitignore +14 -1
  42. package/src/layers/git/index.js +6 -5
  43. package/src/layers/github-actions/index.js +12 -8
  44. package/src/layers/index.js +1 -1
  45. package/src/layers/inspector-support/index.js +101 -0
  46. package/src/layers/prettier/files/.prettierignore +4 -0
  47. package/src/layers/publint/index.js +68 -0
  48. package/src/layers/qunit/files/tests/unit/macros-test.ts +2 -2
  49. package/src/layers/qunit/index.js +107 -7
  50. package/src/layers/qunit/library-files/config/test/babel.config.js +41 -0
  51. package/src/layers/qunit/library-files/config/test/testem.cjs +26 -0
  52. package/src/layers/qunit/library-files/tests/test-helper.ts +36 -0
  53. package/src/layers/qunit/library-files/tests/unit/.gitkeep +0 -0
  54. package/src/layers/qunit/library-files/vite.config.mjs +6 -0
  55. package/src/layers/typescript/index.js +43 -7
  56. package/src/layers/vitest/files/vitest.config.mjs +11 -11
  57. package/src/layers/vitest/index.js +70 -32
  58. package/src/utils/babel.js +9 -6
  59. package/src/utils/fs.js +44 -3
  60. package/src/utils/inspector-support.js +196 -0
  61. package/src/utils/npm.js +21 -7
  62. package/src/utils/remove-types.js +5 -0
  63. package/src/utils/rewrite-imports.js +157 -0
  64. package/src/utils/stage.js +317 -0
  65. package/src/utils/tsdown-config.js +85 -0
  66. package/src/utils/types.ts +5 -1
  67. package/test/ember.nvp/base-library.test.ts +232 -0
  68. package/test/ember.nvp/generator-replace-or-update.test.ts +2 -3
  69. package/test/ember.nvp/inspector-support.test.ts +179 -0
  70. package/test/ember.nvp/layer-inspector-support.test.ts +69 -0
  71. package/test/ember.nvp/layer-publish-checks.test.ts +103 -0
  72. package/test/ember.nvp/layer-qunit.test.ts +376 -15
  73. package/test/ember.nvp/layer-vitest.test.ts +250 -0
  74. package/test/ember.nvp/layers.test.ts +16 -0
  75. package/test/ember.nvp/library-src-fixtures.ts +81 -0
  76. package/test/ember.nvp/permutations/app-1.test.ts +3 -0
  77. package/test/ember.nvp/permutations/app-2.test.ts +3 -0
  78. package/test/ember.nvp/permutations/library-1.test.ts +3 -0
  79. package/test/ember.nvp/permutations/library-2.test.ts +3 -0
  80. package/test/ember.nvp/permutations/shared.ts +239 -0
  81. package/test/ember.nvp/rewrite-imports.test.ts +235 -0
  82. package/test/ember.nvp/stage.test.ts +188 -0
  83. package/test/test-helpers/permutate.test.ts +17 -0
  84. package/test/test-helpers.ts +20 -7
  85. package/tsconfig.json +1 -0
  86. package/packages/vite/src/maybe-babel.ts +0 -219
  87. package/src/layers/qunit/files/run-tests.mjs +0 -30
  88. package/src/layers/vitest/files/tests/test-helper.ts +0 -5
  89. package/test/ember.nvp/permutations.test.ts +0 -200
  90. /package/src/{layers/vitest/files/tests/integration/.gitkeep → bases/minimal-library/files/src/index.ts} +0 -0
  91. /package/src/layers/{vitest/files/tests/unit → qunit/library-files/tests/rendering}/.gitkeep +0 -0
@@ -0,0 +1,115 @@
1
+ # `@nullvoxpopuli/ember-rolldown`
2
+
3
+ A batteries-included meta-plugin for building Ember v2 libraries (addons) with
4
+ [rolldown](https://rolldown.rs/) — or [tsdown](https://tsdown.dev/), which is
5
+ built on top of rolldown and also emits `.d.ts` files.
6
+
7
+ It compiles `.gts`/`.gjs` and template-tag (`<template>`) source into
8
+ publishable output, so a single `ember()` call replaces the usual stack of
9
+ `@embroider/*` externals handling, content-tag preprocessing, and babel wiring.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm add --save-dev @nullvoxpopuli/ember-rolldown
15
+ ```
16
+
17
+ Requires node 24+, and — since these packages ship TypeScript source — a
18
+ modern TypeScript when type-checking: 6+ with `lib` covering `es2025` (e.g.
19
+ `esnext`).
20
+
21
+ ## Usage
22
+
23
+ In your `tsdown.config.js` (recommended — emits declarations):
24
+
25
+ ```js
26
+ import { defineConfig, ember } from "@nullvoxpopuli/ember-rolldown";
27
+
28
+ export default defineConfig({
29
+ entry: ["./src/index.ts"],
30
+ plugins: [ember()],
31
+ });
32
+ ```
33
+
34
+ This builds your entry to `dist/*.js` and `dist/*.d.ts` with sourcemaps,
35
+ cleaning `dist/` between builds and leaving the ember virtual packages to the
36
+ consuming app. You choose the `entry` and `plugins`; any tsdown option you
37
+ pass wins.
38
+
39
+ Or in a plain `rolldown.config.js`:
40
+
41
+ ```js
42
+ import { defineConfig } from "rolldown";
43
+ import { ember } from "@nullvoxpopuli/ember-rolldown";
44
+
45
+ export default defineConfig({
46
+ input: ["src/index.ts"],
47
+ plugins: [ember()],
48
+ });
49
+ ```
50
+
51
+ A `babel.config.js` is optional. Without one, `ember()` compiles templates
52
+ (to `precompileTemplate`), decorators (via
53
+ [decorator-transforms](https://github.com/ef4/decorator-transforms)), and
54
+ TypeScript; with one, your config runs instead.
55
+
56
+ ### Declarations
57
+
58
+ Declarations are emitted with isolated declarations — the only declaration
59
+ pipeline that can see `<template>` (`.gts`/`.gjs`) modules, which exist only
60
+ inside the bundler's module graph. Your `tsconfig.json` must enable it
61
+ (`ember()` errors otherwise):
62
+
63
+ ```jsonc
64
+ {
65
+ "compilerOptions": {
66
+ "isolatedDeclarations": true,
67
+ },
68
+ }
69
+ ```
70
+
71
+ Isolated declarations means every exported value carries an explicit type
72
+ annotation. For template-only components:
73
+
74
+ ```gts
75
+ import type { TOC } from "@ember/component/template-only";
76
+
77
+ export const Badge: TOC<BadgeSignature> = <template>...</template>;
78
+ ```
79
+
80
+ ## What `ember()` does
81
+
82
+ `ember()` returns an array of rolldown plugins:
83
+
84
+ - **`emberIsolatedDeclarations()`** — errors when a `tsconfig.json` is present
85
+ without `isolatedDeclarations: true`.
86
+ - **`emberExternals()`** — keeps your `dependencies`, `peerDependencies`, and
87
+ the ember virtual packages (e.g. `@ember/component`, `@glimmer/tracking`, the
88
+ template compiler) external, so the consuming app resolves them.
89
+ - **`emberTransform()`** — preprocesses `<template>` via
90
+ [content-tag](https://github.com/embroider-build/content-tag) and maps
91
+ `.gts`/`.gjs` to `.ts`/`.js` so rolldown understands them. Also rewrites
92
+ `.gts` specifiers in emitted `.d.ts` files.
93
+ - **`emberBabel()`** — runs babel with `babelHelpers: "bundled"`, but only on
94
+ the files that actually need it (template-tag, decorators, template imports);
95
+ everything else stays on rolldown's fast native (oxc) transform.
96
+
97
+ ## Configuration
98
+
99
+ ```ts
100
+ ember({
101
+ babel: {
102
+ configFile: "./babel.config.js",
103
+ babelHelpers: "bundled",
104
+ plugins: [],
105
+ filter: { include: { imports: ["ember-concurrency"], code: [] } },
106
+ },
107
+ });
108
+ ```
109
+
110
+ Each option is documented on `BabelOptions`.
111
+
112
+ ## Credit
113
+
114
+ The `emberExternals` and `emberTransform` plugins are derived from
115
+ [embroider-build/embroider#2658](https://github.com/embroider-build/embroider/pull/2658).
@@ -0,0 +1,56 @@
1
+ import type { Plugin } from "rolldown";
2
+
3
+ import { emberBabel, type BabelOptions } from "./src/babel.ts";
4
+ import { emberExternals } from "./src/externals.ts";
5
+ import { emberIsolatedDeclarations } from "./src/isolated-declarations.ts";
6
+ import { emberTransform } from "./src/transform.ts";
7
+
8
+ export { defineConfig } from "./src/define-config.ts";
9
+
10
+ interface Config {
11
+ /**
12
+ * Options for the babel step; see `BabelOptions`.
13
+ */
14
+ babel?: BabelOptions;
15
+ }
16
+
17
+ /**
18
+ * A batteries-included plugin for building Ember v2 libraries (addons) with
19
+ * rolldown (or tsdown, which is built on rolldown).
20
+ *
21
+ * It bundles everything needed to compile `.gts`/`.gjs` and template-tag
22
+ * (`<template>`) source into publishable output:
23
+ *
24
+ * - `emberIsolatedDeclarations()` — errors when a tsconfig.json is present
25
+ * without `isolatedDeclarations: true` (required: it is the only
26
+ * declaration pipeline that can see compiled template-tag modules).
27
+ * - `emberExternals()` — keeps your dependencies, peerDependencies, and the
28
+ * ember virtual packages external, so consuming apps resolve them.
29
+ * - `emberTransform()` — preprocesses `<template>` via content-tag and maps
30
+ * `.gts`/`.gjs` to `.ts`/`.js` so rolldown can understand them.
31
+ * - `emberBabel()` — runs babel (template compilation, decorators, type
32
+ * stripping) with `babelHelpers: "bundled"`, but only on the files that
33
+ * actually need it (via `maybeBabel`); everything else stays on the fast
34
+ * native transform. The library's own `babel.config.js` is used when it
35
+ * exists; no config file is required.
36
+ *
37
+ * Usage in `tsdown.config.js` / `rolldown.config.js`:
38
+ *
39
+ * ```js
40
+ * import { ember } from "@nullvoxpopuli/ember-rolldown";
41
+ *
42
+ * export default defineConfig({
43
+ * entry: ["./src/index.ts"],
44
+ * dts: true,
45
+ * plugins: [ember()],
46
+ * });
47
+ * ```
48
+ */
49
+ export function ember(config: Config = {}): Plugin[] {
50
+ return [
51
+ emberIsolatedDeclarations(),
52
+ emberExternals(),
53
+ emberTransform(),
54
+ emberBabel(config.babel),
55
+ ];
56
+ }
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@nullvoxpopuli/ember-rolldown",
3
+ "version": "1.1.0",
4
+ "description": "Meta plugin for building ember v2 libraries/addons with rolldown & tsdown.",
5
+ "type": "module",
6
+ "main": "index.ts",
7
+ "publishConfig": {
8
+ "main": "dist/index.js"
9
+ },
10
+ "keywords": [
11
+ "rolldown",
12
+ "tsdown",
13
+ "ember",
14
+ "meta plugin",
15
+ "v2 addon",
16
+ "library"
17
+ ],
18
+ "author": "NullVoxPopuli",
19
+ "license": "MIT",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/NullVoxPopuli/ember.nvp",
23
+ "directory": "packages/rolldown"
24
+ },
25
+ "scripts": {
26
+ "build": "rolldown --config",
27
+ "prepack": "pnpm build"
28
+ },
29
+ "packageManager": "pnpm@10.28.0",
30
+ "dependencies": {
31
+ "@babel/core": "^7.28.10",
32
+ "@babel/plugin-transform-typescript": "^7.28.10",
33
+ "@embroider/core": "^4.4.2",
34
+ "@nullvoxpopuli/ember-build-tooling-utils": "workspace:*",
35
+ "babel-plugin-ember-template-compilation": "^4.0.0",
36
+ "content-tag": "^4.2.0",
37
+ "decorator-transforms": "^2.3.2"
38
+ },
39
+ "engines": {
40
+ "node": ">= 24"
41
+ },
42
+ "peerDependencies": {
43
+ "tsdown": ">= 0.22.0",
44
+ "typescript": ">= 5.5"
45
+ },
46
+ "peerDependenciesMeta": {
47
+ "tsdown": {
48
+ "optional": true
49
+ },
50
+ "typescript": {
51
+ "optional": true
52
+ }
53
+ },
54
+ "devDependencies": {
55
+ "rolldown": "^1.1.2",
56
+ "tsdown": "^0.22.7",
57
+ "typescript": "^6.0.3"
58
+ }
59
+ }
@@ -0,0 +1,21 @@
1
+ import { defineConfig } from "rolldown";
2
+
3
+ import manifest from "./package.json" with { type: "json" };
4
+
5
+ const deps = [
6
+ ...Object.keys(manifest.dependencies || {}),
7
+ ...Object.keys(manifest.peerDependencies || {}),
8
+ ];
9
+
10
+ export default defineConfig({
11
+ input: "index.ts",
12
+ output: {
13
+ format: "esm",
14
+ file: "dist/index.js",
15
+ codeSplitting: false,
16
+ },
17
+ external(id) {
18
+ if (id.startsWith("node:")) return true;
19
+ if (deps.some((dep) => id.startsWith(dep))) return true;
20
+ },
21
+ });
@@ -0,0 +1,136 @@
1
+ import { maybeBabel } from "@nullvoxpopuli/ember-build-tooling-utils";
2
+ import transformTypeScript from "@babel/plugin-transform-typescript";
3
+ import templateCompilation from "babel-plugin-ember-template-compilation";
4
+ import decoratorTransforms from "decorator-transforms";
5
+ import { loadPartialConfigSync } from "@babel/core";
6
+ import type { PluginItem } from "@babel/core";
7
+ import type { Plugin } from "rolldown";
8
+
9
+ export interface BabelOptions {
10
+ babelHelpers?: "bundled" | "runtime" | "inline" | "external";
11
+ /**
12
+ * The babel config file to use.
13
+ *
14
+ * - `undefined`: auto-detect the project's babel config (via babel's own
15
+ * config resolution).
16
+ * - a string: use that config file.
17
+ * - `false`: ignore config files entirely.
18
+ *
19
+ * Without a config file, templates, decorators, and TypeScript are still
20
+ * handled (see `defaultPlugins`).
21
+ */
22
+ configFile?: string | false;
23
+ /**
24
+ * Extra babel plugins to run, appended after the config's plugins.
25
+ */
26
+ plugins?: PluginItem[];
27
+ /**
28
+ * Opt additional files into babel (see maybeBabel's `filter`). Use this to
29
+ * cover addons that ship code needing babel (e.g. `ember-concurrency`).
30
+ */
31
+ filter?: {
32
+ include: {
33
+ imports: string[];
34
+ code: (string | RegExp)[];
35
+ };
36
+ };
37
+ }
38
+
39
+ /**
40
+ * Resolve the project's root babel config the way babel itself does
41
+ * (`babel.config.{js,cjs,mjs,cts,json}`, honoring `rootMode` semantics).
42
+ *
43
+ * The sync API evaluates the config file; on node 24+ that works for native
44
+ * ESM configs too (require(esm)).
45
+ */
46
+ function detectConfigFile(): string | undefined {
47
+ const partial = loadPartialConfigSync({ cwd: process.cwd() });
48
+
49
+ return partial?.config ?? undefined;
50
+ }
51
+
52
+ /**
53
+ * Everything a library without its own babel config needs:
54
+ *
55
+ * - TypeScript stripping
56
+ * - template compilation to `precompileTemplate` (`targetFormat: "hbs"`).
57
+ * Published libraries must NOT ship wire format: the wire format is
58
+ * private between the template compiler and the glimmer runtime of the
59
+ * same version, so baking it in ties the published artifact to the
60
+ * consuming app's exact ember-source. The consuming app performs final
61
+ * compilation.
62
+ * - decorator-transforms, with its runtime left as a bare specifier so the
63
+ * consuming app resolves it (the library keeps `decorator-transforms` as
64
+ * a real dependency).
65
+ */
66
+ function defaultPlugins(): PluginItem[] {
67
+ return [
68
+ [
69
+ transformTypeScript,
70
+ {
71
+ allExtensions: true,
72
+ onlyRemoveTypeImports: true,
73
+ allowDeclareFields: true,
74
+ },
75
+ ],
76
+ [templateCompilation, { targetFormat: "hbs" }],
77
+ [
78
+ decoratorTransforms,
79
+ {
80
+ // Emit `import ... from "decorator-transforms/runtime-esm"` as a bare
81
+ // specifier so it stays external and the consuming app resolves it.
82
+ runtime: { import: "decorator-transforms/runtime-esm" },
83
+ },
84
+ ],
85
+ ];
86
+ }
87
+
88
+ /**
89
+ * We still need babel (not just oxc/rolldown's native transforms) for two
90
+ * reasons:
91
+ *
92
+ * - template compilation: `babel-plugin-ember-template-compilation` turns the
93
+ * content-tag output into `precompileTemplate` calls; oxc has no equivalent.
94
+ * - decorators: Ember uses the legacy/stage-1 decorator signature together
95
+ * with `decorator-transforms`, which rewrites decorated class fields
96
+ * (`@tracked x`) into getter/setters backed by a runtime. oxc's native
97
+ * "legacy decorators" emit tsc-style `__decorate` output, which does not
98
+ * match those semantics.
99
+ *
100
+ * But we don't want to send *every* file through babel and give back the speed
101
+ * rolldown/oxc buys us -- so we run babel via `maybeBabel`, which filters down
102
+ * to only the files that actually need it (template-tag, decorators, template
103
+ * imports). Everything else stays on the native transform.
104
+ *
105
+ * The library's own babel config is used when it exists; without one,
106
+ * `defaultPlugins` covers templates, decorators, and TypeScript, so no
107
+ * config file is required.
108
+ *
109
+ * Libraries default to `babelHelpers: "bundled"` so the emitted output is
110
+ * self-contained.
111
+ */
112
+ export function emberBabel(options: BabelOptions = {}): Plugin {
113
+ const configFile = options.configFile === undefined ? detectConfigFile() : options.configFile;
114
+
115
+ const shared = {
116
+ babelHelpers: options.babelHelpers ?? "bundled",
117
+ filter: options.filter,
118
+ };
119
+
120
+ if (configFile) {
121
+ return maybeBabel({
122
+ ...shared,
123
+ configFile,
124
+ plugins: options.plugins ?? [],
125
+ }) as unknown as Plugin;
126
+ }
127
+
128
+ return maybeBabel({
129
+ ...shared,
130
+ // No config file (or the caller opted out): don't let babel go looking
131
+ // for one -- we provide the whole plugin list inline.
132
+ configFile: false,
133
+ babelrc: false,
134
+ plugins: defaultPlugins().concat(options.plugins ?? []),
135
+ }) as unknown as Plugin;
136
+ }
@@ -0,0 +1,32 @@
1
+ import type { UserConfig } from "tsdown";
2
+
3
+ /**
4
+ * tsdown's config, set up for an Ember v2 library: builds your entry to
5
+ * `dist/*.js` and `dist/*.d.ts` with sourcemaps, cleaning `dist/` between
6
+ * builds and leaving the ember virtual packages to the consuming app.
7
+ *
8
+ * You choose the `entry` and the `plugins` (e.g. `ember()`); any tsdown
9
+ * option you pass wins.
10
+ *
11
+ * ```js
12
+ * import { defineConfig, ember } from "@nullvoxpopuli/ember-rolldown";
13
+ *
14
+ * export default defineConfig({
15
+ * entry: ["./src/index.ts"],
16
+ * plugins: [ember()],
17
+ * });
18
+ * ```
19
+ */
20
+ export function defineConfig(config: UserConfig): UserConfig {
21
+ return {
22
+ sourcemap: true,
23
+ clean: true,
24
+ dts: true,
25
+ // tsdown's extensions are .mjs/.d.mts otherwise, even in type-module
26
+ // packages; exports maps conventionally point at .js/.d.ts
27
+ outExtensions: () => ({ js: ".js", dts: ".d.ts" }),
28
+ // provided by the consuming app
29
+ neverBundle: ["node:*", "@ember/*", "@glimmer/*"],
30
+ ...config,
31
+ };
32
+ }
@@ -0,0 +1,83 @@
1
+ import { readFileSync } from "node:fs";
2
+
3
+ import type { Plugin } from "rolldown";
4
+
5
+ // @embroider/core is cjs, so we default-import and destructure.
6
+ import pkg from "@embroider/core";
7
+ const { emberVirtualPackages, emberVirtualPeerDeps, packageName, templateCompilationModules } = pkg;
8
+
9
+ const compilationModules = new Set(templateCompilationModules.map((m) => m.module));
10
+
11
+ /**
12
+ * @param {string} path
13
+ */
14
+ function readJsonSync(path: string) {
15
+ return JSON.parse(readFileSync(path, { encoding: "utf8" }));
16
+ }
17
+
18
+ /**
19
+ * Everything the library declares as a `dependency` or `peerDependency` is
20
+ * resolvable by the consuming app, so we never bundle it.
21
+ */
22
+ function resolvableDependencies(): Set<string> {
23
+ const deps = new Set<string>();
24
+ const manifest = readJsonSync("package.json");
25
+
26
+ for (const name of Object.keys(manifest.dependencies ?? {})) {
27
+ deps.add(name);
28
+ }
29
+ for (const name of Object.keys(manifest.peerDependencies ?? {})) {
30
+ deps.add(name);
31
+ }
32
+
33
+ return deps;
34
+ }
35
+
36
+ /**
37
+ * Keeps the library's declared dependencies and the ember virtual packages
38
+ * (things like `@ember/component`, `@glimmer/tracking`, the template compiler,
39
+ * …) external, so the app that consumes the library resolves them instead of
40
+ * the library bundling copies of them.
41
+ */
42
+ export function emberExternals(): Plugin {
43
+ let deps: Set<string>;
44
+
45
+ return {
46
+ name: "ember:externals",
47
+
48
+ buildStart() {
49
+ this.addWatchFile("package.json");
50
+ deps = resolvableDependencies();
51
+ },
52
+
53
+ resolveId: {
54
+ order: "pre",
55
+ handler(source) {
56
+ // Anything with a protocol (`node:`, virtual modules, …) is not ours
57
+ // to externalize.
58
+ if (source.includes(":")) {
59
+ return null;
60
+ }
61
+
62
+ const pkgName = packageName(source);
63
+
64
+ if (!pkgName) {
65
+ // No package name means a relative import, which we don't deal with.
66
+ return null;
67
+ }
68
+
69
+ if (
70
+ deps.has(pkgName) ||
71
+ emberVirtualPeerDeps.has(pkgName) ||
72
+ emberVirtualPackages.has(pkgName) ||
73
+ compilationModules.has(pkgName)
74
+ ) {
75
+ // `false` tells rolldown to treat the id as external.
76
+ return false;
77
+ }
78
+
79
+ return undefined;
80
+ },
81
+ },
82
+ };
83
+ }
@@ -0,0 +1,65 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+
4
+ import type { Plugin } from "rolldown";
5
+
6
+ /**
7
+ * Errors the build when a `tsconfig.json` is present without
8
+ * `isolatedDeclarations: true`.
9
+ *
10
+ * This is required: declarations for `.gts`/`.gjs` (template tag) modules can
11
+ * only be emitted by the isolated-declarations pipeline, which reads compiled
12
+ * modules from the bundler's module graph. The `tsc`-based pipeline reads
13
+ * source files from disk, where the compiled modules don't exist, and fails
14
+ * with "Source file not found".
15
+ *
16
+ * Failing fast here -- rather than only when declaration emit happens to run
17
+ * -- keeps the project honest: editors and `tsc`/`ember-tsc` then check the
18
+ * same rules the build enforces (exported values need explicit type
19
+ * annotations, e.g. `export const X: TOC<Sig> = <template>...`).
20
+ *
21
+ * Projects without a `tsconfig.json` (JavaScript libraries) or without the
22
+ * `typescript` package have no declarations to emit, so there is nothing to
23
+ * check.
24
+ */
25
+ export function emberIsolatedDeclarations(): Plugin {
26
+ return {
27
+ name: "ember:isolated-declarations",
28
+
29
+ async buildStart() {
30
+ const tsconfigPath = resolve("tsconfig.json");
31
+
32
+ if (!existsSync(tsconfigPath)) return;
33
+
34
+ let ts: typeof import("typescript");
35
+
36
+ try {
37
+ ts = (await import("typescript")).default;
38
+ } catch {
39
+ return;
40
+ }
41
+
42
+ const { config, error } = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
43
+
44
+ if (error) {
45
+ // Unreadable/unparsable tsconfig: tsc itself will report this with a
46
+ // better message than we can.
47
+ return;
48
+ }
49
+
50
+ // Resolves `extends` chains, so the flag may come from a base config.
51
+ const parsed = ts.parseJsonConfigFileContent(config, ts.sys, dirname(tsconfigPath));
52
+
53
+ if (parsed.options.isolatedDeclarations !== true) {
54
+ this.error(
55
+ `${tsconfigPath} must set "compilerOptions.isolatedDeclarations": true.\n\n` +
56
+ `Declarations for .gts/.gjs (template tag) modules can only be emitted by the ` +
57
+ `isolated-declarations pipeline, which reads compiled modules from the bundler's ` +
58
+ `module graph -- the tsc-based pipeline reads from disk and cannot see them.\n\n` +
59
+ `Isolated declarations require exported values to have explicit type annotations, ` +
60
+ `e.g. \`export const X: TOC<Sig> = <template>...\`.`,
61
+ );
62
+ }
63
+ },
64
+ };
65
+ }