ember.nvp 1.6.0 → 1.7.1
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/.github/workflows/ci.yml +5 -1
- package/.release-plan.json +51 -9
- package/CHANGELOG.md +30 -0
- package/README.md +41 -28
- package/RELEASE.md +16 -14
- package/package.json +1 -1
- package/packages/build-tooling-utils/README.md +7 -12
- package/packages/build-tooling-utils/package.json +1 -1
- package/packages/build-tooling-utils/src/extensions.ts +4 -3
- package/packages/build-tooling-utils/src/maybe-babel.ts +18 -15
- package/packages/rolldown/README.md +138 -98
- package/packages/rolldown/index.ts +66 -28
- package/packages/rolldown/package.json +1 -1
- package/packages/rolldown/src/app-reexports.test.ts +4 -3
- package/packages/rolldown/src/app-reexports.ts +37 -27
- package/packages/rolldown/src/babel.test.ts +10 -5
- package/packages/rolldown/src/babel.ts +117 -72
- package/packages/rolldown/src/bundle.test.ts +149 -0
- package/packages/rolldown/src/bundle.ts +71 -0
- package/packages/rolldown/src/config.ts +42 -18
- package/packages/rolldown/src/dts.test.ts +42 -26
- package/packages/rolldown/src/externals.test.ts +16 -11
- package/packages/rolldown/src/externals.ts +45 -28
- package/packages/rolldown/src/isolated-declarations.ts +56 -43
- package/packages/rolldown/src/plugin-like.ts +18 -15
- package/packages/rolldown/src/published-types.test.ts +18 -13
- package/packages/rolldown/src/transform.test.ts +26 -17
- package/packages/rolldown/src/transform.ts +68 -48
- package/packages/vite/README.md +10 -11
- package/packages/vite/package.json +1 -1
- package/packages/vite/src/ember.ts +44 -36
- package/packages/vite/src/test-html.ts +13 -11
- package/src/bases/README.md +6 -3
- package/src/bases/minimal-app/app.test.ts +7 -4
- package/src/bases/minimal-app/files/app/app.ts +2 -3
- package/src/bases/minimal-app/files/app/router.ts +3 -3
- package/src/bases/minimal-app/index.js +10 -6
- package/src/bases/minimal-custom-element/files/README.md +95 -0
- package/src/bases/minimal-custom-element/files/package.json +47 -0
- package/src/bases/minimal-custom-element/files/src/components/counter.gts +27 -0
- package/src/bases/minimal-custom-element/files/src/element.ts +45 -0
- package/src/bases/minimal-custom-element/files/src/index.ts +2 -0
- package/src/bases/minimal-custom-element/files/src/register.ts +5 -0
- package/src/bases/minimal-custom-element/files/tsconfig.json +10 -0
- package/src/bases/minimal-custom-element/files/tsdown.config.js +7 -0
- package/src/bases/minimal-custom-element/index.js +212 -0
- package/src/bases/minimal-extension/files/README.md +13 -5
- package/src/bases/minimal-extension/files/app/app.ts +2 -3
- package/src/bases/minimal-extension/files/app/config.ts +5 -3
- package/src/bases/minimal-extension/index.js +16 -11
- package/src/bases/minimal-library/files/README.md +8 -7
- package/src/bases/minimal-library/index.js +21 -15
- package/src/cli/args.js +1 -1
- package/src/cli/generator.js +4 -0
- package/src/cli/index.js +23 -18
- package/src/cli/questions/project-type.js +17 -2
- package/src/cli/questions/write.js +12 -10
- package/src/layers/are-the-types-wrong/index.js +6 -6
- package/src/layers/github-actions/index.js +7 -4
- package/src/layers/inspector-support/index.js +2 -2
- package/src/layers/publint/index.js +2 -2
- package/src/layers/qunit/index.js +21 -17
- package/src/layers/readme/index.js +55 -4
- package/src/layers/renovate/index.js +5 -5
- package/src/layers/typescript/index.js +20 -13
- package/src/layers/vitest/index.js +8 -4
- package/src/utils/babel.js +3 -2
- package/src/utils/buffer.js +2 -2
- package/src/utils/cli.js +4 -4
- package/src/utils/fs.js +23 -14
- package/src/utils/inspector-support.js +22 -19
- package/src/utils/npm.js +15 -12
- package/src/utils/project-type.js +19 -0
- package/src/utils/project.js +14 -2
- package/src/utils/remove-types.js +2 -4
- package/src/utils/rewrite-imports.js +43 -31
- package/src/utils/stage.js +44 -33
- package/src/utils/tsdown-config.js +6 -5
- package/src/utils/types.ts +10 -9
- package/test/ember.nvp/base-custom-element.test.ts +440 -0
- package/test/ember.nvp/base-library.test.ts +6 -4
- package/test/ember.nvp/generator-replace-or-update.test.ts +6 -4
- package/test/ember.nvp/inspector-support.test.ts +4 -3
- package/test/ember.nvp/layer-inspector-support.test.ts +2 -3
- package/test/ember.nvp/layer-publish-checks.test.ts +2 -3
- package/test/ember.nvp/layer-qunit.test.ts +2 -2
- package/test/ember.nvp/layer-readme.test.ts +63 -3
- package/test/ember.nvp/layer-vitest.test.ts +16 -9
- package/test/ember.nvp/layers-load.test.ts +26 -0
- package/test/ember.nvp/library-src-fixtures.ts +7 -3
- package/test/ember.nvp/permutations/custom-element-1.test.ts +3 -0
- package/test/ember.nvp/permutations/custom-element-2.test.ts +3 -0
- package/test/ember.nvp/permutations/custom-element-checks.test.ts +3 -0
- package/test/ember.nvp/permutations/shared.ts +22 -12
- package/test/ember.nvp/publish-configs.test.ts +44 -31
- package/test/ember.nvp/rewrite-imports.test.ts +4 -3
- package/test/ember.nvp/scoped-css-library.test.ts +6 -4
- package/test/test-helpers/permutate.test.ts +3 -3
- package/test/test-helpers.ts +12 -7
|
@@ -15,8 +15,9 @@ afterEach(() => {
|
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* A babel config whose only plugin rewrites the string `"MARKER"` to `name
|
|
19
|
-
*
|
|
18
|
+
* A babel config whose only plugin rewrites the string `"MARKER"` to `name`.
|
|
19
|
+
*
|
|
20
|
+
* So the emitted code says which config file babel loaded.
|
|
20
21
|
*/
|
|
21
22
|
function markerConfig(name: string): string {
|
|
22
23
|
return [
|
|
@@ -37,9 +38,13 @@ function markerConfig(name: string): string {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
|
-
* Builds a `.gts` entry
|
|
41
|
-
*
|
|
42
|
-
*
|
|
41
|
+
* Builds a `.gts` entry in a temp dir that becomes the cwd,
|
|
42
|
+
* the way config detection sees it under the tsdown CLI.
|
|
43
|
+
*
|
|
44
|
+
* A `.gts` entry so babel runs at all:
|
|
45
|
+
* `maybeBabel` skips files that need no transform.
|
|
46
|
+
*
|
|
47
|
+
* Returns the emitted code.
|
|
43
48
|
*/
|
|
44
49
|
async function bundleWithBabel(
|
|
45
50
|
files: Record<string, string>,
|
|
@@ -10,18 +10,33 @@ import type { Transform } from "babel-plugin-ember-template-compilation";
|
|
|
10
10
|
import type { Plugin } from "rolldown";
|
|
11
11
|
|
|
12
12
|
export interface BabelOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Compile templates to the wire format instead of `precompileTemplate`.
|
|
15
|
+
*
|
|
16
|
+
* For a build that bundles its own ember-source.
|
|
17
|
+
* The wire format is private between one template compiler and one
|
|
18
|
+
* glimmer runtime of the same version, so this is only safe when both
|
|
19
|
+
* ship together.
|
|
20
|
+
*
|
|
21
|
+
* The compiler is ember-source's own, resolved from the package.
|
|
22
|
+
*
|
|
23
|
+
* Set by `ember({ bundle: true })`.
|
|
24
|
+
* Only applies without a babel config file.
|
|
25
|
+
*/
|
|
26
|
+
bundle?: boolean;
|
|
13
27
|
babelHelpers?: "bundled" | "runtime" | "inline" | "external";
|
|
14
28
|
/**
|
|
15
29
|
* The babel config file to use.
|
|
16
30
|
*
|
|
17
|
-
* - `undefined`: auto-detect.
|
|
18
|
-
*
|
|
19
|
-
*
|
|
31
|
+
* - `undefined`: auto-detect.
|
|
32
|
+
* A `babel.publish.config.*` (in the package root or `config/`) wins,
|
|
33
|
+
* because this is a publish build.
|
|
34
|
+
* Otherwise babel's own config resolution applies (see `detectConfigFile`).
|
|
20
35
|
* - a string: use that config file.
|
|
21
36
|
* - `false`: ignore config files entirely.
|
|
22
37
|
*
|
|
23
|
-
* Without a config file, templates, decorators, and TypeScript are still
|
|
24
|
-
*
|
|
38
|
+
* Without a config file, templates, decorators, and TypeScript are still handled
|
|
39
|
+
* (see `defaultPlugins`).
|
|
25
40
|
*/
|
|
26
41
|
configFile?: string | false;
|
|
27
42
|
/**
|
|
@@ -29,17 +44,20 @@ export interface BabelOptions {
|
|
|
29
44
|
*/
|
|
30
45
|
plugins?: PluginItem[];
|
|
31
46
|
/**
|
|
32
|
-
* Template AST transforms
|
|
33
|
-
*
|
|
47
|
+
* Template AST transforms passed to the default
|
|
48
|
+
* `babel-plugin-ember-template-compilation` entry.
|
|
49
|
+
* e.g. ember-scoped-css's `scopedCSS.template()`
|
|
34
50
|
*
|
|
35
|
-
* Only valid without a babel config file
|
|
36
|
-
* `babel-plugin-ember-template-compilation` yourself,
|
|
37
|
-
* belong there.
|
|
51
|
+
* Only valid without a babel config file.
|
|
52
|
+
* With your own config, you list `babel-plugin-ember-template-compilation` yourself,
|
|
53
|
+
* so its `transforms` belong there.
|
|
38
54
|
*/
|
|
39
55
|
templateTransforms?: Transform[];
|
|
40
56
|
/**
|
|
41
|
-
* Opt additional files into babel (see maybeBabel's `filter`).
|
|
42
|
-
*
|
|
57
|
+
* Opt additional files into babel (see maybeBabel's `filter`).
|
|
58
|
+
*
|
|
59
|
+
* Use this to cover addons that ship code needing babel
|
|
60
|
+
* (e.g. `ember-concurrency`).
|
|
43
61
|
*/
|
|
44
62
|
filter?: {
|
|
45
63
|
include: {
|
|
@@ -49,27 +67,34 @@ export interface BabelOptions {
|
|
|
49
67
|
};
|
|
50
68
|
}
|
|
51
69
|
|
|
52
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Tried in order, so an ESM config wins over a JSON one of the same name.
|
|
72
|
+
*/
|
|
53
73
|
const PUBLISH_CONFIG_EXTENSIONS = ["mjs", "cjs", "js", "mts", "cts", "ts", "json"];
|
|
54
74
|
|
|
55
75
|
/**
|
|
56
|
-
* Tried in order:
|
|
57
|
-
*
|
|
76
|
+
* Tried in order:
|
|
77
|
+
* - next to package.json first
|
|
78
|
+
* - then `config/`, for libraries that keep their build configuration
|
|
79
|
+
* out of the package root
|
|
58
80
|
*/
|
|
59
81
|
const PUBLISH_CONFIG_DIRECTORIES = [".", "config"];
|
|
60
82
|
|
|
61
83
|
/**
|
|
62
|
-
* A `babel.publish.config.*` (in the package root or `config/`), if there is
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* compiles `@embroider/macros` away
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
84
|
+
* A `babel.publish.config.*` (in the package root or `config/`), if there is one.
|
|
85
|
+
*
|
|
86
|
+
* A library's plain `babel.config.*` is its *development* config.
|
|
87
|
+
* It typically:
|
|
88
|
+
* - compiles `@embroider/macros` away
|
|
89
|
+
* - targets the wire format
|
|
90
|
+
* - wires up whatever the in-package demo app or test suite needs
|
|
91
|
+
*
|
|
92
|
+
* None of that belongs in a published artifact:
|
|
93
|
+
* - macros must stay, for the consuming app to evaluate
|
|
94
|
+
* - the wire format is private between one template compiler and one glimmer runtime
|
|
95
|
+
*
|
|
96
|
+
* Babel's own resolution can't tell the two apart (it finds whatever `babel.config.*` exists),
|
|
97
|
+
* so a publish build has to prefer the config named for publishing.
|
|
73
98
|
*/
|
|
74
99
|
function detectPublishConfigFile(): string | undefined {
|
|
75
100
|
for (const directory of PUBLISH_CONFIG_DIRECTORIES) {
|
|
@@ -84,14 +109,16 @@ function detectPublishConfigFile(): string | undefined {
|
|
|
84
109
|
}
|
|
85
110
|
|
|
86
111
|
/**
|
|
87
|
-
* The config this publish build
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
112
|
+
* The config this publish build must use:
|
|
113
|
+
* - a `babel.publish.config.*`, when the library has one
|
|
114
|
+
* - else the project's root babel config, resolved the way babel itself does
|
|
115
|
+
* (`babel.config.{js,cjs,mjs,cts,json}`, honoring `rootMode` semantics)
|
|
116
|
+
*
|
|
117
|
+
* For a library whose only config *is* its publish config,
|
|
118
|
+
* that is the same file either way.
|
|
119
|
+
*
|
|
120
|
+
* The sync API evaluates the config file.
|
|
121
|
+
* On node 24+ that works for native ESM configs too (require(esm)).
|
|
95
122
|
*/
|
|
96
123
|
function detectConfigFile(): string | undefined {
|
|
97
124
|
const publishConfig = detectPublishConfigFile();
|
|
@@ -107,17 +134,25 @@ function detectConfigFile(): string | undefined {
|
|
|
107
134
|
* Everything a library without its own babel config needs:
|
|
108
135
|
*
|
|
109
136
|
* - TypeScript stripping
|
|
137
|
+
*
|
|
110
138
|
* - template compilation to `precompileTemplate` (`targetFormat: "hbs"`).
|
|
111
|
-
* Published libraries must NOT ship wire format
|
|
112
|
-
* private between the template compiler and the glimmer runtime
|
|
113
|
-
* same version, so baking it in ties the published artifact
|
|
114
|
-
* consuming app's exact ember-source.
|
|
115
|
-
* compilation.
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
139
|
+
* Published libraries must NOT ship wire format.
|
|
140
|
+
* The wire format is private between the template compiler and the glimmer runtime
|
|
141
|
+
* of the same version, so baking it in ties the published artifact
|
|
142
|
+
* to the consuming app's exact ember-source.
|
|
143
|
+
* The consuming app performs final compilation.
|
|
144
|
+
*
|
|
145
|
+
* A bundle-mode build ships that exact ember-source,
|
|
146
|
+
* so it compiles to the wire format with ember-source's own compiler,
|
|
147
|
+
* and needs no compiler at runtime.
|
|
148
|
+
*
|
|
149
|
+
* - decorator-transforms, with its runtime left as a bare specifier
|
|
150
|
+
* so the consuming app resolves it.
|
|
151
|
+
* (the library keeps `decorator-transforms` as a real dependency)
|
|
152
|
+
*
|
|
153
|
+
* A bundle-mode build resolves it itself.
|
|
119
154
|
*/
|
|
120
|
-
function defaultPlugins(templateTransforms?: Transform[]): PluginItem[] {
|
|
155
|
+
function defaultPlugins(templateTransforms?: Transform[], bundle = false): PluginItem[] {
|
|
121
156
|
return [
|
|
122
157
|
[
|
|
123
158
|
transformTypeScript,
|
|
@@ -127,12 +162,15 @@ function defaultPlugins(templateTransforms?: Transform[]): PluginItem[] {
|
|
|
127
162
|
allowDeclareFields: true,
|
|
128
163
|
},
|
|
129
164
|
],
|
|
130
|
-
[
|
|
165
|
+
[
|
|
166
|
+
templateCompilation,
|
|
167
|
+
{ targetFormat: bundle ? "wire" : "hbs", transforms: templateTransforms ?? [] },
|
|
168
|
+
],
|
|
131
169
|
[
|
|
132
170
|
decoratorTransforms,
|
|
133
171
|
{
|
|
134
|
-
// Emit `import ... from "decorator-transforms/runtime-esm"` as a bare
|
|
135
|
-
//
|
|
172
|
+
// Emit `import ... from "decorator-transforms/runtime-esm"` as a bare specifier,
|
|
173
|
+
// so it stays external and the consuming app resolves it.
|
|
136
174
|
runtime: { import: "decorator-transforms/runtime-esm" },
|
|
137
175
|
},
|
|
138
176
|
],
|
|
@@ -140,29 +178,34 @@ function defaultPlugins(templateTransforms?: Transform[]): PluginItem[] {
|
|
|
140
178
|
}
|
|
141
179
|
|
|
142
180
|
/**
|
|
143
|
-
* We still need babel (not just oxc/rolldown's native transforms) for two
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
181
|
+
* We still need babel (not just oxc / rolldown's native transforms) for two reasons:
|
|
182
|
+
*
|
|
183
|
+
* - template compilation.
|
|
184
|
+
* `babel-plugin-ember-template-compilation` turns the content-tag output
|
|
185
|
+
* into `precompileTemplate` calls.
|
|
186
|
+
* oxc has no equivalent.
|
|
187
|
+
*
|
|
188
|
+
* - decorators.
|
|
189
|
+
* Ember uses the legacy / stage-1 decorator signature together with `decorator-transforms`,
|
|
190
|
+
* which rewrites decorated class fields (`@tracked x`) into getter / setters backed by a runtime.
|
|
191
|
+
* oxc's native "legacy decorators" emit tsc-style `__decorate` output,
|
|
192
|
+
* which does not match those semantics.
|
|
193
|
+
*
|
|
194
|
+
* But we don't want to send *every* file through babel
|
|
195
|
+
* and give back the speed rolldown / oxc buys us.
|
|
196
|
+
*
|
|
197
|
+
* So we run babel via `maybeBabel`, which filters down to only the files that need it
|
|
198
|
+
* (template-tag, decorators, template imports).
|
|
199
|
+
* Everything else stays on the native transform.
|
|
200
|
+
*
|
|
201
|
+
* The library's own babel config is used when it exists.
|
|
202
|
+
* Its `babel.publish.config.*` (root or `config/`) wins over its `babel.config.*`,
|
|
203
|
+
* since this is a publish build.
|
|
204
|
+
*
|
|
205
|
+
* Without one, `defaultPlugins` covers templates, decorators, and TypeScript,
|
|
206
|
+
* so no config file is required.
|
|
207
|
+
*
|
|
208
|
+
* Libraries default to `babelHelpers: "bundled"`, so the emitted output is self-contained.
|
|
166
209
|
*/
|
|
167
210
|
export function emberBabel(options: BabelOptions = {}): Plugin {
|
|
168
211
|
const configFile = options.configFile === undefined ? detectConfigFile() : options.configFile;
|
|
@@ -190,10 +233,12 @@ export function emberBabel(options: BabelOptions = {}): Plugin {
|
|
|
190
233
|
|
|
191
234
|
return maybeBabel({
|
|
192
235
|
...shared,
|
|
193
|
-
// No config file (or the caller opted out)
|
|
194
|
-
// for one
|
|
236
|
+
// No config file (or the caller opted out).
|
|
237
|
+
// Don't let babel go looking for one: we provide the whole plugin list inline.
|
|
195
238
|
configFile: false,
|
|
196
239
|
babelrc: false,
|
|
197
|
-
plugins: defaultPlugins(options.templateTransforms).concat(
|
|
240
|
+
plugins: defaultPlugins(options.templateTransforms, options.bundle).concat(
|
|
241
|
+
options.plugins ?? [],
|
|
242
|
+
),
|
|
198
243
|
}) as unknown as Plugin;
|
|
199
244
|
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { rolldown } from "rolldown";
|
|
5
|
+
import type { InputOptions } from "rolldown";
|
|
6
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
7
|
+
|
|
8
|
+
import { emberBundle, emberSourceAliases } from "./bundle.ts";
|
|
9
|
+
|
|
10
|
+
let restoreCwd: (() => void) | undefined;
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
restoreCwd?.();
|
|
14
|
+
restoreCwd = undefined;
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* An ember-source stub shaped like the real package:
|
|
19
|
+
* - a `./*` export whose conditions pick one of two builds
|
|
20
|
+
* - a `renamed-modules` map keyed by module file path
|
|
21
|
+
*/
|
|
22
|
+
const EMBER_SOURCE_MANIFEST = {
|
|
23
|
+
name: "ember-source",
|
|
24
|
+
version: "0.0.0",
|
|
25
|
+
type: "module",
|
|
26
|
+
exports: {
|
|
27
|
+
"./*": {
|
|
28
|
+
development: "./dist/dev/packages/*",
|
|
29
|
+
production: "./dist/prod/packages/*",
|
|
30
|
+
default: "./dist/prod/packages/*",
|
|
31
|
+
},
|
|
32
|
+
"./package.json": "./package.json",
|
|
33
|
+
},
|
|
34
|
+
"ember-addon": {
|
|
35
|
+
"renamed-modules": {
|
|
36
|
+
"@ember/modifier/index.js": "ember-source/@ember/modifier/index.js",
|
|
37
|
+
"@ember/modifier/on.js": "ember-source/@ember/modifier/on.js",
|
|
38
|
+
"@ember/-internals/glimmer/index.js": "ember-source/@ember/-internals/glimmer/index.js",
|
|
39
|
+
"@glimmer/runtime/index.js": "ember-source/@glimmer/runtime/index.js",
|
|
40
|
+
"rsvp/index.js": "ember-source/rsvp/index.js",
|
|
41
|
+
"ember/index.js": "ember-source/ember/index.js",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Each provided module exists in both builds, and says which build it is.
|
|
48
|
+
*/
|
|
49
|
+
const EMBER_SOURCE_MODULES = {
|
|
50
|
+
"@ember/modifier/index.js": `export const on = "on from BUILD";`,
|
|
51
|
+
"@ember/modifier/on.js": `export const on = "on from BUILD";`,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
async function fixture({ emberSource = true } = {}): Promise<string> {
|
|
55
|
+
const dir = await mkdtemp(path.join(tmpdir(), "ember-rolldown-bundle-"));
|
|
56
|
+
await writeFile(
|
|
57
|
+
path.join(dir, "package.json"),
|
|
58
|
+
JSON.stringify({ name: "fixture", version: "0.0.0", type: "module" }),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (emberSource) {
|
|
62
|
+
const stubDir = path.join(dir, "node_modules/ember-source");
|
|
63
|
+
await mkdir(stubDir, { recursive: true });
|
|
64
|
+
await writeFile(path.join(stubDir, "package.json"), JSON.stringify(EMBER_SOURCE_MANIFEST));
|
|
65
|
+
|
|
66
|
+
for (const build of ["dev", "prod"]) {
|
|
67
|
+
for (const [modulePath, source] of Object.entries(EMBER_SOURCE_MODULES)) {
|
|
68
|
+
const file = path.join(stubDir, "dist", build, "packages", modulePath);
|
|
69
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
70
|
+
await writeFile(file, source.replace("BUILD", build));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const previousCwd = process.cwd();
|
|
76
|
+
process.chdir(dir);
|
|
77
|
+
restoreCwd = () => process.chdir(previousCwd);
|
|
78
|
+
|
|
79
|
+
return dir;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function bundle(dir: string, entry: string, resolve: InputOptions["resolve"] = {}) {
|
|
83
|
+
await writeFile(path.join(dir, "index.js"), entry);
|
|
84
|
+
|
|
85
|
+
const build = await rolldown({
|
|
86
|
+
input: path.join(dir, "index.js"),
|
|
87
|
+
plugins: [emberBundle()],
|
|
88
|
+
resolve,
|
|
89
|
+
onwarn() {},
|
|
90
|
+
});
|
|
91
|
+
const { output } = await build.generate({ format: "es" });
|
|
92
|
+
|
|
93
|
+
return output.filter((chunk) => "code" in chunk).map((chunk) => chunk.code)[0] ?? "";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
describe("emberSourceAliases", () => {
|
|
97
|
+
it("aliases each provided package into ember-source", async () => {
|
|
98
|
+
await fixture();
|
|
99
|
+
|
|
100
|
+
expect(emberSourceAliases()).toEqual({
|
|
101
|
+
"@ember/modifier": "ember-source/@ember/modifier",
|
|
102
|
+
"@ember/-internals": "ember-source/@ember/-internals",
|
|
103
|
+
"@glimmer/runtime": "ember-source/@glimmer/runtime",
|
|
104
|
+
rsvp: "ember-source/rsvp",
|
|
105
|
+
ember: "ember-source/ember",
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("has nothing to alias without ember-source in the graph", async () => {
|
|
110
|
+
await fixture({ emberSource: false });
|
|
111
|
+
|
|
112
|
+
expect(emberSourceAliases()).toEqual({});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe("emberBundle", () => {
|
|
117
|
+
it("bundles a provided package's index and module files from the production build", async () => {
|
|
118
|
+
const dir = await fixture();
|
|
119
|
+
|
|
120
|
+
const code = await bundle(
|
|
121
|
+
dir,
|
|
122
|
+
`import { on } from "@ember/modifier";\nimport { on as onModule } from "@ember/modifier/on";\nexport { on, onModule };`,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
expect(code).toContain(`"on from prod"`);
|
|
126
|
+
expect(code).not.toContain(`from "@ember/modifier`);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("follows the development condition", async () => {
|
|
130
|
+
const dir = await fixture();
|
|
131
|
+
|
|
132
|
+
const code = await bundle(dir, `export { on } from "@ember/modifier";`, {
|
|
133
|
+
conditionNames: ["development"],
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
expect(code).toContain(`"on from dev"`);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("keeps explicit aliases", async () => {
|
|
140
|
+
const dir = await fixture();
|
|
141
|
+
await writeFile(path.join(dir, "mine.js"), `export const on = "mine";`);
|
|
142
|
+
|
|
143
|
+
const code = await bundle(dir, `export { on } from "@ember/modifier";`, {
|
|
144
|
+
alias: { "@ember/modifier": path.join(dir, "mine.js") },
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
expect(code).toContain(`"mine"`);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Plugin } from "rolldown";
|
|
2
|
+
|
|
3
|
+
import { emberSourceRenamedModules } from "./externals.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The package name a renamed-modules key belongs to
|
|
7
|
+
* `@ember/modifier/on.js` → `@ember/modifier`
|
|
8
|
+
* `rsvp/index.js` → `rsvp`
|
|
9
|
+
*/
|
|
10
|
+
function packageOf(modulePath: string): string {
|
|
11
|
+
const segments = modulePath.split("/");
|
|
12
|
+
const length = modulePath.startsWith("@") ? 2 : 1;
|
|
13
|
+
|
|
14
|
+
return segments.slice(0, length).join("/");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* One alias per package ember-source provides, pointing into ember-source
|
|
19
|
+
* `@ember/modifier` → `ember-source/@ember/modifier`
|
|
20
|
+
*
|
|
21
|
+
* The provided packages are the ones named in `ember-addon.renamed-modules`
|
|
22
|
+
* (`@ember/*`, `@glimmer/tracking`, `rsvp`, `backburner.js`, …).
|
|
23
|
+
*
|
|
24
|
+
* Real packages such as `@glimmer/component` are not in it,
|
|
25
|
+
* so they keep resolving from node_modules.
|
|
26
|
+
*/
|
|
27
|
+
export function emberSourceAliases(): Record<string, string> {
|
|
28
|
+
const aliases: Record<string, string> = {};
|
|
29
|
+
|
|
30
|
+
for (const modulePath of Object.keys(emberSourceRenamedModules())) {
|
|
31
|
+
const name = packageOf(modulePath);
|
|
32
|
+
|
|
33
|
+
aliases[name] = `ember-source/${name}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return aliases;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Makes the modules ember-source provides resolvable,
|
|
41
|
+
* so a bundle-mode build can include them.
|
|
42
|
+
*
|
|
43
|
+
* ember-source exports every module under `./*`,
|
|
44
|
+
* with `development` and `production` conditions selecting one of its two builds.
|
|
45
|
+
*
|
|
46
|
+
* A bare `@ember/modifier` is not that path, though.
|
|
47
|
+
* It only exists as `ember-source/@ember/modifier`.
|
|
48
|
+
*
|
|
49
|
+
* So this plugin adds the alias for each provided package,
|
|
50
|
+
* and leaves the rest to the resolver:
|
|
51
|
+
* - the `default` condition selects the production build
|
|
52
|
+
* - `resolve.conditionNames: ["development"]` selects the development build
|
|
53
|
+
* (tsdown: `inputOptions.resolve.conditionNames`)
|
|
54
|
+
*
|
|
55
|
+
* Aliases set explicitly in the build config win over these.
|
|
56
|
+
*/
|
|
57
|
+
export function emberBundle(): Plugin {
|
|
58
|
+
return {
|
|
59
|
+
name: "ember:bundle",
|
|
60
|
+
|
|
61
|
+
options(inputOptions) {
|
|
62
|
+
inputOptions.resolve ??= {};
|
|
63
|
+
inputOptions.resolve.alias = {
|
|
64
|
+
...emberSourceAliases(),
|
|
65
|
+
...inputOptions.resolve.alias,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return inputOptions;
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -1,33 +1,48 @@
|
|
|
1
1
|
import type { TsdownPlugin, UserConfig } from "tsdown";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Sensible defaults for an Ember v2 library
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Sensible defaults for an Ember v2 library.
|
|
5
|
+
*
|
|
6
|
+
* Applied through plugin hooks, so they travel with `ember()`.
|
|
7
|
+
* You get them whether you use this package's `defineConfig`, tsdown's,
|
|
8
|
+
* or a plain `tsdown.config.js` / `rolldown.config.js`.
|
|
7
9
|
*
|
|
8
10
|
* Every value is applied with `??=`, so anything you set explicitly wins.
|
|
9
11
|
*
|
|
10
|
-
* Under tsdown, the full set applies via the `tsdownConfig` hook
|
|
11
|
-
* Vite's `config` hook):
|
|
12
|
+
* Under tsdown, the full set applies via the `tsdownConfig` hook
|
|
13
|
+
* (analogous to Vite's `config` hook):
|
|
12
14
|
*
|
|
13
|
-
* - `sourcemap` — emit sourcemaps alongside the output
|
|
14
|
-
* - `clean` — wipe `dist/` between builds
|
|
15
|
-
* - `dts` — emit `.d.ts` declarations (via isolated declarations)
|
|
16
|
-
* - `outExtensions` — `.js
|
|
17
|
-
*
|
|
18
|
-
* - `report` — off
|
|
19
|
-
*
|
|
20
|
-
*
|
|
15
|
+
* - `sourcemap` — emit sourcemaps alongside the output
|
|
16
|
+
* - `clean` — wipe `dist/` between builds
|
|
17
|
+
* - `dts` — emit `.d.ts` declarations (via isolated declarations)
|
|
18
|
+
* - `outExtensions` — `.js` / `.d.ts` rather than tsdown's default `.mjs` / `.d.mts`
|
|
19
|
+
* (exports maps conventionally point at `.js` / `.d.ts`)
|
|
20
|
+
* - `report` — off.
|
|
21
|
+
* The size report is noise for a library build.
|
|
22
|
+
* - `deps.neverBundle` — leave node builtins and the ember virtual packages
|
|
23
|
+
* to the consuming app (`emberExternals()` handles the rest)
|
|
21
24
|
*
|
|
22
|
-
* A plain rolldown build has no notion of `clean
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
+
* A plain rolldown build has no notion of `clean` / `dts` / `outExtensions` / `report`.
|
|
26
|
+
* Those are tsdown-level concepts.
|
|
27
|
+
* So only the options with rolldown equivalents are applied there,
|
|
28
|
+
* via rolldown's own `outputOptions` hook:
|
|
25
29
|
*
|
|
26
|
-
* - `output.sourcemap` — on
|
|
30
|
+
* - `output.sourcemap` — on
|
|
27
31
|
*
|
|
28
32
|
* Externals are handled by `emberExternals()` (a `resolveId` hook) in both cases.
|
|
33
|
+
*
|
|
34
|
+
* Bundle mode flips the dependency defaults.
|
|
35
|
+
* The output is a self-contained browser artifact, so:
|
|
36
|
+
*
|
|
37
|
+
* - `platform` — `browser`
|
|
38
|
+
* - `deps.alwaysBundle` — everything but node builtins
|
|
39
|
+
* (including the package's own `dependencies` and `peerDependencies`,
|
|
40
|
+
* and the declarations' type imports)
|
|
41
|
+
* - `deps.neverBundle` — node builtins only
|
|
42
|
+
* - `deps.onlyBundle` — `false`
|
|
43
|
+
* (bundling node_modules is the point, so tsdown's hint about it is noise)
|
|
29
44
|
*/
|
|
30
|
-
export function emberConfig(): TsdownPlugin {
|
|
45
|
+
export function emberConfig({ bundle = false }: { bundle?: boolean } = {}): TsdownPlugin {
|
|
31
46
|
return {
|
|
32
47
|
name: "ember:config",
|
|
33
48
|
|
|
@@ -39,6 +54,15 @@ export function emberConfig(): TsdownPlugin {
|
|
|
39
54
|
config.report ??= false;
|
|
40
55
|
|
|
41
56
|
config.deps ??= {};
|
|
57
|
+
|
|
58
|
+
if (bundle) {
|
|
59
|
+
config.platform ??= "browser";
|
|
60
|
+
config.deps.alwaysBundle ??= (id) => !id.startsWith("node:");
|
|
61
|
+
config.deps.neverBundle ??= ["node:*"];
|
|
62
|
+
config.deps.onlyBundle ??= false;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
42
66
|
config.deps.neverBundle ??= ["node:*", "@ember/*", "@glimmer/*"];
|
|
43
67
|
},
|
|
44
68
|
|