ember.nvp 1.1.0 → 1.2.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 +6 -18
- package/CHANGELOG.md +17 -0
- package/package.json +1 -1
- package/packages/rolldown/README.md +42 -4
- package/packages/rolldown/package.json +1 -1
- package/packages/rolldown/src/babel.test.ts +17 -0
- package/packages/rolldown/src/babel.ts +21 -3
- package/packages/rolldown/src/dts.test.ts +238 -38
- package/packages/rolldown/src/transform.test.ts +35 -0
- package/packages/rolldown/src/transform.ts +64 -31
- package/test/ember.nvp/scoped-css-library.test.ts +211 -0
package/.release-plan.json
CHANGED
|
@@ -2,21 +2,13 @@
|
|
|
2
2
|
"solution": {
|
|
3
3
|
"ember.nvp": {
|
|
4
4
|
"impact": "minor",
|
|
5
|
-
"oldVersion": "1.
|
|
6
|
-
"newVersion": "1.
|
|
5
|
+
"oldVersion": "1.1.0",
|
|
6
|
+
"newVersion": "1.2.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 :memo: Documentation"
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"impact": "patch",
|
|
19
|
-
"reason": "Appears in changelog section :house: Internal"
|
|
20
12
|
}
|
|
21
13
|
],
|
|
22
14
|
"pkgJSONPath": "./package.json"
|
|
@@ -26,8 +18,8 @@
|
|
|
26
18
|
},
|
|
27
19
|
"@nullvoxpopuli/ember-rolldown": {
|
|
28
20
|
"impact": "minor",
|
|
29
|
-
"oldVersion": "2.
|
|
30
|
-
"newVersion": "2.
|
|
21
|
+
"oldVersion": "2.1.0",
|
|
22
|
+
"newVersion": "2.2.0",
|
|
31
23
|
"tagName": "latest",
|
|
32
24
|
"constraints": [
|
|
33
25
|
{
|
|
@@ -36,11 +28,7 @@
|
|
|
36
28
|
},
|
|
37
29
|
{
|
|
38
30
|
"impact": "patch",
|
|
39
|
-
"reason": "Appears in changelog section :
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
"impact": "patch",
|
|
43
|
-
"reason": "Appears in changelog section :house: Internal"
|
|
31
|
+
"reason": "Appears in changelog section :bug: Bug Fix"
|
|
44
32
|
}
|
|
45
33
|
],
|
|
46
34
|
"pkgJSONPath": "./packages/rolldown/package.json"
|
|
@@ -49,5 +37,5 @@
|
|
|
49
37
|
"oldVersion": "1.1.0"
|
|
50
38
|
}
|
|
51
39
|
},
|
|
52
|
-
"description": "## Release (2026-07-22)\n\n* ember.nvp 1.
|
|
40
|
+
"description": "## Release (2026-07-22)\n\n* ember.nvp 1.2.0 (minor)\n* @nullvoxpopuli/ember-rolldown 2.2.0 (minor)\n\n#### :rocket: Enhancement\n* `@nullvoxpopuli/ember-rolldown`, `ember.nvp`\n * [#98](https://github.com/NullVoxPopuli/ember.nvp/pull/98) Integration-test ember-scoped-css; add babel.templateTransforms ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n\n#### :bug: Bug Fix\n* `@nullvoxpopuli/ember-rolldown`\n * [#99](https://github.com/NullVoxPopuli/ember.nvp/pull/99) rolldown: fix virtual .gts declaration resolution + specifier-rewrite sourcemaps ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### Committers: 2\n- @NullVoxPopuli's reduced-access machine account for AI usage ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n"
|
|
53
41
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## Release (2026-07-22)
|
|
4
4
|
|
|
5
|
+
* ember.nvp 1.2.0 (minor)
|
|
6
|
+
* @nullvoxpopuli/ember-rolldown 2.2.0 (minor)
|
|
7
|
+
|
|
8
|
+
#### :rocket: Enhancement
|
|
9
|
+
* `@nullvoxpopuli/ember-rolldown`, `ember.nvp`
|
|
10
|
+
* [#98](https://github.com/NullVoxPopuli/ember.nvp/pull/98) Integration-test ember-scoped-css; add babel.templateTransforms ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))
|
|
11
|
+
|
|
12
|
+
#### :bug: Bug Fix
|
|
13
|
+
* `@nullvoxpopuli/ember-rolldown`
|
|
14
|
+
* [#99](https://github.com/NullVoxPopuli/ember.nvp/pull/99) rolldown: fix virtual .gts declaration resolution + specifier-rewrite sourcemaps ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
|
|
15
|
+
|
|
16
|
+
#### Committers: 2
|
|
17
|
+
- @NullVoxPopuli's reduced-access machine account for AI usage ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))
|
|
18
|
+
- [@NullVoxPopuli](https://github.com/NullVoxPopuli)
|
|
19
|
+
|
|
20
|
+
## Release (2026-07-22)
|
|
21
|
+
|
|
5
22
|
* ember.nvp 1.1.0 (minor)
|
|
6
23
|
* @nullvoxpopuli/ember-rolldown 2.1.0 (minor)
|
|
7
24
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ember.nvp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.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",
|
|
@@ -99,9 +99,42 @@ because the importing component module never loads, any declaration that
|
|
|
99
99
|
references that component dangles, surfacing as misleading
|
|
100
100
|
`UNLOADABLE_DEPENDENCY` errors on `<component>.d.ts` files.
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
### ember-scoped-css
|
|
103
|
+
|
|
104
|
+
[ember-scoped-css](https://github.com/auditboard/ember-scoped-css) works with
|
|
105
|
+
this pipeline: its template transform rides along via `ember()`'s
|
|
106
|
+
`babel.templateTransforms` option, and its unplugin (`ember-scoped-css/rollup`)
|
|
107
|
+
resolves the scoped CSS requests the transform injects:
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
import { defineConfig } from "tsdown";
|
|
111
|
+
import { ember } from "@nullvoxpopuli/ember-rolldown";
|
|
112
|
+
import { scopedCSS } from "ember-scoped-css/rollup";
|
|
113
|
+
import { scopedCSS as scopedCssBabel } from "ember-scoped-css/babel";
|
|
114
|
+
|
|
115
|
+
export default defineConfig({
|
|
116
|
+
entry: ["./src/index.ts"],
|
|
117
|
+
css: { inject: true },
|
|
118
|
+
plugins: [
|
|
119
|
+
ember({
|
|
120
|
+
babel: {
|
|
121
|
+
plugins: [scopedCssBabel()],
|
|
122
|
+
templateTransforms: [scopedCssBabel.template({})],
|
|
123
|
+
},
|
|
124
|
+
}),
|
|
125
|
+
scopedCSS(),
|
|
126
|
+
],
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
This scopes co-located `.css` files, inline `<style scoped>` blocks, and the
|
|
131
|
+
`scopedClass` pseudo-helper (that last one is what the `babel.plugins` entry
|
|
132
|
+
handles — leave it off if you don't use `scopedClass` in module code).
|
|
133
|
+
|
|
134
|
+
`css.inject` matters for libraries: it keeps the `import "./style.css"`
|
|
135
|
+
statement in `dist/index.js`, so consuming apps pull the styles in through
|
|
136
|
+
the module graph — without it the bundled CSS is emitted but nothing loads
|
|
137
|
+
it.
|
|
105
138
|
|
|
106
139
|
## What `ember()` does
|
|
107
140
|
|
|
@@ -128,12 +161,17 @@ ember({
|
|
|
128
161
|
configFile: "./babel.config.js",
|
|
129
162
|
babelHelpers: "bundled",
|
|
130
163
|
plugins: [],
|
|
164
|
+
templateTransforms: [],
|
|
131
165
|
filter: { include: { imports: ["ember-concurrency"], code: [] } },
|
|
132
166
|
},
|
|
133
167
|
});
|
|
134
168
|
```
|
|
135
169
|
|
|
136
|
-
Each option is documented on `BabelOptions`.
|
|
170
|
+
Each option is documented on `BabelOptions`. `templateTransforms` feeds
|
|
171
|
+
template AST transforms to the default template-compilation step; it can't be
|
|
172
|
+
combined with a babel config file — a config lists
|
|
173
|
+
`babel-plugin-ember-template-compilation` itself, so its transforms belong
|
|
174
|
+
there.
|
|
137
175
|
|
|
138
176
|
## App re-exports
|
|
139
177
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { emberBabel } from "./babel.ts";
|
|
4
|
+
|
|
5
|
+
describe("emberBabel", () => {
|
|
6
|
+
it("rejects templateTransforms combined with a config file", () => {
|
|
7
|
+
expect(() =>
|
|
8
|
+
emberBabel({ configFile: "./babel.config.js", templateTransforms: ["some-transform"] }),
|
|
9
|
+
).toThrow(/pass the transforms/);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("allows templateTransforms when config files are opted out", () => {
|
|
13
|
+
expect(() =>
|
|
14
|
+
emberBabel({ configFile: false, templateTransforms: ["some-transform"] }),
|
|
15
|
+
).not.toThrow();
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -4,6 +4,7 @@ import templateCompilation from "babel-plugin-ember-template-compilation";
|
|
|
4
4
|
import decoratorTransforms from "decorator-transforms";
|
|
5
5
|
import { loadPartialConfigSync } from "@babel/core";
|
|
6
6
|
import type { PluginItem } from "@babel/core";
|
|
7
|
+
import type { Transform } from "babel-plugin-ember-template-compilation";
|
|
7
8
|
import type { Plugin } from "rolldown";
|
|
8
9
|
|
|
9
10
|
export interface BabelOptions {
|
|
@@ -24,6 +25,15 @@ export interface BabelOptions {
|
|
|
24
25
|
* Extra babel plugins to run, appended after the config's plugins.
|
|
25
26
|
*/
|
|
26
27
|
plugins?: PluginItem[];
|
|
28
|
+
/**
|
|
29
|
+
* Template AST transforms (e.g. ember-scoped-css's `scopedCSS.template()`)
|
|
30
|
+
* passed to the default `babel-plugin-ember-template-compilation` entry.
|
|
31
|
+
*
|
|
32
|
+
* Only valid without a babel config file: with your own config, you list
|
|
33
|
+
* `babel-plugin-ember-template-compilation` yourself, so its `transforms`
|
|
34
|
+
* belong there.
|
|
35
|
+
*/
|
|
36
|
+
templateTransforms?: Transform[];
|
|
27
37
|
/**
|
|
28
38
|
* Opt additional files into babel (see maybeBabel's `filter`). Use this to
|
|
29
39
|
* cover addons that ship code needing babel (e.g. `ember-concurrency`).
|
|
@@ -63,7 +73,7 @@ function detectConfigFile(): string | undefined {
|
|
|
63
73
|
* consuming app resolves it (the library keeps `decorator-transforms` as
|
|
64
74
|
* a real dependency).
|
|
65
75
|
*/
|
|
66
|
-
function defaultPlugins(): PluginItem[] {
|
|
76
|
+
function defaultPlugins(templateTransforms?: Transform[]): PluginItem[] {
|
|
67
77
|
return [
|
|
68
78
|
[
|
|
69
79
|
transformTypeScript,
|
|
@@ -73,7 +83,7 @@ function defaultPlugins(): PluginItem[] {
|
|
|
73
83
|
allowDeclareFields: true,
|
|
74
84
|
},
|
|
75
85
|
],
|
|
76
|
-
[templateCompilation, { targetFormat: "hbs" }],
|
|
86
|
+
[templateCompilation, { targetFormat: "hbs", transforms: templateTransforms ?? [] }],
|
|
77
87
|
[
|
|
78
88
|
decoratorTransforms,
|
|
79
89
|
{
|
|
@@ -118,6 +128,14 @@ export function emberBabel(options: BabelOptions = {}): Plugin {
|
|
|
118
128
|
};
|
|
119
129
|
|
|
120
130
|
if (configFile) {
|
|
131
|
+
if (options.templateTransforms?.length) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`\`babel.templateTransforms\` cannot be combined with a babel config file (found ${configFile}). ` +
|
|
134
|
+
`Your config lists babel-plugin-ember-template-compilation itself, so pass the transforms ` +
|
|
135
|
+
`to its \`transforms\` option there instead.`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
121
139
|
return maybeBabel({
|
|
122
140
|
...shared,
|
|
123
141
|
configFile,
|
|
@@ -131,6 +149,6 @@ export function emberBabel(options: BabelOptions = {}): Plugin {
|
|
|
131
149
|
// for one -- we provide the whole plugin list inline.
|
|
132
150
|
configFile: false,
|
|
133
151
|
babelrc: false,
|
|
134
|
-
plugins: defaultPlugins().concat(options.plugins ?? []),
|
|
152
|
+
plugins: defaultPlugins(options.templateTransforms).concat(options.plugins ?? []),
|
|
135
153
|
}) as unknown as Plugin;
|
|
136
154
|
}
|
|
@@ -6,13 +6,30 @@ import { afterEach, describe, expect, it } from "vitest";
|
|
|
6
6
|
|
|
7
7
|
import { ember } from "../index.ts";
|
|
8
8
|
|
|
9
|
+
interface Dist {
|
|
10
|
+
/** Sorted dist-relative paths of every emitted file. */
|
|
11
|
+
files: string[];
|
|
12
|
+
/** dist-relative path -> content, for every emitted .d.ts and .css file. */
|
|
13
|
+
contents: Record<string, string>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let restoreCwd: (() => void) | undefined;
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
restoreCwd?.();
|
|
20
|
+
restoreCwd = undefined;
|
|
21
|
+
});
|
|
22
|
+
|
|
9
23
|
/**
|
|
10
24
|
* Drives `ember()` through a real tsdown build (the dts pipeline included).
|
|
11
25
|
* `files` is a map of relative path -> source, written into a temp dir that
|
|
12
26
|
* becomes the cwd for the build (the externals and isolated-declarations
|
|
13
27
|
* plugins read package.json / tsconfig.json from cwd, like the tsdown CLI).
|
|
28
|
+
*
|
|
29
|
+
* Returns the emitted file list plus the full contents of every declaration
|
|
30
|
+
* and css file (js output is covered by transform.test.ts).
|
|
14
31
|
*/
|
|
15
|
-
async function buildFixture(files: Record<string, string>, entry: string[]): Promise<
|
|
32
|
+
async function buildFixture(files: Record<string, string>, entry: string[]): Promise<Dist> {
|
|
16
33
|
const dir = await mkdtemp(path.join(tmpdir(), "ember-rolldown-dts-"));
|
|
17
34
|
|
|
18
35
|
const withDefaults: Record<string, string> = {
|
|
@@ -45,33 +62,40 @@ async function buildFixture(files: Record<string, string>, entry: string[]): Pro
|
|
|
45
62
|
plugins: [ember()],
|
|
46
63
|
});
|
|
47
64
|
|
|
48
|
-
|
|
49
|
-
}
|
|
65
|
+
const distDir = path.join(dir, "dist");
|
|
66
|
+
const dist: Dist = { files: [], contents: {} };
|
|
50
67
|
|
|
51
|
-
|
|
68
|
+
for (const entry of await readdir(distDir, { withFileTypes: true, recursive: true })) {
|
|
69
|
+
if (!entry.isFile()) continue;
|
|
52
70
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
restoreCwd = undefined;
|
|
56
|
-
});
|
|
71
|
+
const relative = path.relative(distDir, path.join(entry.parentPath, entry.name));
|
|
72
|
+
dist.files.push(relative);
|
|
57
73
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
for (const entry of await readdir(dir, { withFileTypes: true, recursive: true })) {
|
|
61
|
-
if (entry.isFile()) {
|
|
62
|
-
out.push(path.relative(dir, path.join(entry.parentPath, entry.name)));
|
|
74
|
+
if (relative.endsWith(".d.ts") || relative.endsWith(".css")) {
|
|
75
|
+
dist.contents[relative] = await readFile(path.join(distDir, relative), "utf8");
|
|
63
76
|
}
|
|
64
77
|
}
|
|
65
|
-
|
|
78
|
+
|
|
79
|
+
dist.files.sort();
|
|
80
|
+
|
|
81
|
+
return dist;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** One printable document of every captured file, for a single snapshot. */
|
|
85
|
+
function printed(dist: Dist): string {
|
|
86
|
+
return Object.entries(dist.contents)
|
|
87
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
88
|
+
.map(([file, content]) => `//=== ${file}\n${content}`)
|
|
89
|
+
.join("\n");
|
|
66
90
|
}
|
|
67
91
|
|
|
68
92
|
describe("declarations (full ember() via tsdown)", () => {
|
|
69
93
|
it("emits .d.ts for an entry that type-imports from a non-entry .gts", async () => {
|
|
70
|
-
// Mirrors
|
|
94
|
+
// Mirrors a design-system shape: the component is in the runtime graph via
|
|
71
95
|
// the index entry, while a second entry (a service) only *type*-imports
|
|
72
96
|
// from it. The service's declaration then references the component's
|
|
73
97
|
// (virtual) .d.ts, which only exists in the bundler's module graph.
|
|
74
|
-
const
|
|
98
|
+
const dist = await buildFixture(
|
|
75
99
|
{
|
|
76
100
|
"src/index.ts": `export { default as Popup } from './popup.gts';`,
|
|
77
101
|
"src/service.ts": [
|
|
@@ -103,17 +127,164 @@ describe("declarations (full ember() via tsdown)", () => {
|
|
|
103
127
|
["./src/index.ts", "./src/service.ts"],
|
|
104
128
|
);
|
|
105
129
|
|
|
106
|
-
|
|
130
|
+
expect(dist.files).toMatchInlineSnapshot(`
|
|
131
|
+
[
|
|
132
|
+
"index.d.ts",
|
|
133
|
+
"index.js",
|
|
134
|
+
"index.js.map",
|
|
135
|
+
"popup-CjMmUEVb.d.ts",
|
|
136
|
+
"popup-CjMmUEVb.d.ts.map",
|
|
137
|
+
"service.d.ts",
|
|
138
|
+
"service.d.ts.map",
|
|
139
|
+
"service.js",
|
|
140
|
+
"service.js.map",
|
|
141
|
+
]
|
|
142
|
+
`);
|
|
107
143
|
|
|
108
|
-
expect(dist).
|
|
109
|
-
|
|
144
|
+
expect(printed(dist)).toMatchInlineSnapshot(`
|
|
145
|
+
"//=== index.d.ts
|
|
146
|
+
import { t as Popup } from "./popup-CjMmUEVb.js";
|
|
147
|
+
export { Popup };
|
|
148
|
+
//=== popup-CjMmUEVb.d.ts
|
|
149
|
+
import Component from "@glimmer/component";
|
|
150
|
+
//#region src/popup.d.ts
|
|
151
|
+
interface PopupBlock {
|
|
152
|
+
open: () => void;
|
|
153
|
+
}
|
|
154
|
+
interface PopupSignature {
|
|
155
|
+
Element: HTMLDivElement;
|
|
156
|
+
Blocks: {
|
|
157
|
+
default: [PopupBlock];
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
declare class Popup extends Component<PopupSignature> {}
|
|
161
|
+
//#endregion
|
|
162
|
+
export { PopupBlock as n, Popup as t };
|
|
163
|
+
//# sourceMappingURL=popup-CjMmUEVb.d.ts.map
|
|
164
|
+
//=== service.d.ts
|
|
165
|
+
import { n as PopupBlock } from "./popup-CjMmUEVb.js";
|
|
166
|
+
//#region src/service.d.ts
|
|
167
|
+
declare class TooltipService {
|
|
168
|
+
registerPopup(context: PopupBlock): void;
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
171
|
+
export { TooltipService };
|
|
172
|
+
//# sourceMappingURL=service.d.ts.map"
|
|
173
|
+
`);
|
|
174
|
+
});
|
|
110
175
|
|
|
111
|
-
|
|
176
|
+
it("emits .d.ts when a .gts module is only ever type-imported", async () => {
|
|
177
|
+
// Nothing value-imports popup.gts, so its virtual module never enters the
|
|
178
|
+
// runtime graph: the only route to its declaration is the dts resolver
|
|
179
|
+
// loading it on demand. The declaration pipeline registers modules as
|
|
180
|
+
// their (virtual) source loads, so the import must resolve to the source
|
|
181
|
+
// id — a pre-rewritten `.d.ts` id skips that registration and dangles.
|
|
182
|
+
const dist = await buildFixture(
|
|
183
|
+
{
|
|
184
|
+
"src/service.ts": [
|
|
185
|
+
`import type { PopupBlock } from './components/popup.gts';`,
|
|
186
|
+
``,
|
|
187
|
+
`export class TooltipService {`,
|
|
188
|
+
` registerPopup(context: PopupBlock): void {`,
|
|
189
|
+
` void context;`,
|
|
190
|
+
` }`,
|
|
191
|
+
`}`,
|
|
192
|
+
].join("\n"),
|
|
193
|
+
"src/components/popup.gts": [
|
|
194
|
+
`import Component from '@glimmer/component';`,
|
|
195
|
+
``,
|
|
196
|
+
`export interface PopupBlock {`,
|
|
197
|
+
` open: () => void;`,
|
|
198
|
+
`}`,
|
|
199
|
+
``,
|
|
200
|
+
`export default class Popup extends Component {`,
|
|
201
|
+
` <template>popup</template>`,
|
|
202
|
+
`}`,
|
|
203
|
+
].join("\n"),
|
|
204
|
+
},
|
|
205
|
+
["./src/service.ts"],
|
|
206
|
+
);
|
|
112
207
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
208
|
+
expect(dist.files).toMatchInlineSnapshot(`
|
|
209
|
+
[
|
|
210
|
+
"service.d.ts",
|
|
211
|
+
"service.d.ts.map",
|
|
212
|
+
"service.js",
|
|
213
|
+
"service.js.map",
|
|
214
|
+
]
|
|
215
|
+
`);
|
|
216
|
+
|
|
217
|
+
expect(printed(dist)).toMatchInlineSnapshot(`
|
|
218
|
+
"//=== service.d.ts
|
|
219
|
+
import "@glimmer/component";
|
|
220
|
+
//#region src/components/popup.d.ts
|
|
221
|
+
interface PopupBlock {
|
|
222
|
+
open: () => void;
|
|
223
|
+
}
|
|
224
|
+
//#endregion
|
|
225
|
+
//#region src/service.d.ts
|
|
226
|
+
declare class TooltipService {
|
|
227
|
+
registerPopup(context: PopupBlock): void;
|
|
228
|
+
}
|
|
229
|
+
//#endregion
|
|
230
|
+
export { TooltipService };
|
|
231
|
+
//# sourceMappingURL=service.d.ts.map"
|
|
232
|
+
`);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("emits .d.ts for a .gts that type-imports another (otherwise unimported) .gts", async () => {
|
|
236
|
+
// A nested component type-imports a sibling from the parent directory;
|
|
237
|
+
// its declaration then carries a relative source specifier for a module
|
|
238
|
+
// that exists only in the bundler's graph (never on disk, never in the
|
|
239
|
+
// runtime graph).
|
|
240
|
+
const dist = await buildFixture(
|
|
241
|
+
{
|
|
242
|
+
"src/index.ts": `export { default as OptionList } from './components/select/option-list.gts';`,
|
|
243
|
+
"src/components/select/option-list.gts": [
|
|
244
|
+
`import Component from '@glimmer/component';`,
|
|
245
|
+
`import type { PopupBlock } from '../popup.gts';`,
|
|
246
|
+
``,
|
|
247
|
+
`export interface OptionListArgs {`,
|
|
248
|
+
` registerPopup: (context: PopupBlock) => void;`,
|
|
249
|
+
`}`,
|
|
250
|
+
``,
|
|
251
|
+
`export default class OptionList extends Component {`,
|
|
252
|
+
` <template>options</template>`,
|
|
253
|
+
`}`,
|
|
254
|
+
].join("\n"),
|
|
255
|
+
"src/components/popup.gts": [
|
|
256
|
+
`import Component from '@glimmer/component';`,
|
|
257
|
+
``,
|
|
258
|
+
`export interface PopupBlock {`,
|
|
259
|
+
` open: () => void;`,
|
|
260
|
+
`}`,
|
|
261
|
+
``,
|
|
262
|
+
`export default class Popup extends Component {`,
|
|
263
|
+
` <template>popup</template>`,
|
|
264
|
+
`}`,
|
|
265
|
+
].join("\n"),
|
|
266
|
+
},
|
|
267
|
+
["./src/index.ts"],
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
expect(dist.files).toMatchInlineSnapshot(`
|
|
271
|
+
[
|
|
272
|
+
"index.d.ts",
|
|
273
|
+
"index.d.ts.map",
|
|
274
|
+
"index.js",
|
|
275
|
+
"index.js.map",
|
|
276
|
+
]
|
|
277
|
+
`);
|
|
278
|
+
|
|
279
|
+
expect(printed(dist)).toMatchInlineSnapshot(`
|
|
280
|
+
"//=== index.d.ts
|
|
281
|
+
import Component from "@glimmer/component";
|
|
282
|
+
//#region src/components/select/option-list.d.ts
|
|
283
|
+
declare class OptionList extends Component {}
|
|
284
|
+
//#endregion
|
|
285
|
+
export { OptionList };
|
|
286
|
+
//# sourceMappingURL=index.d.ts.map"
|
|
287
|
+
`);
|
|
117
288
|
});
|
|
118
289
|
|
|
119
290
|
it("bundles co-located CSS (via @tsdown/css) without breaking declarations", async () => {
|
|
@@ -121,8 +292,8 @@ describe("declarations (full ember() via tsdown)", () => {
|
|
|
121
292
|
// handling the build dies on tsdown's css-guard — and because the failing
|
|
122
293
|
// component module never loads, every declaration that references it
|
|
123
294
|
// dangles (surfacing as misleading UNLOADABLE_DEPENDENCY errors on
|
|
124
|
-
// <component>.d.ts). @tsdown/css
|
|
125
|
-
const
|
|
295
|
+
// <component>.d.ts). @tsdown/css makes both work.
|
|
296
|
+
const dist = await buildFixture(
|
|
126
297
|
{
|
|
127
298
|
"src/index.ts": `export { default as Popup } from './popup.gts';`,
|
|
128
299
|
"src/service.ts": [
|
|
@@ -149,18 +320,47 @@ describe("declarations (full ember() via tsdown)", () => {
|
|
|
149
320
|
["./src/index.ts", "./src/service.ts"],
|
|
150
321
|
);
|
|
151
322
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
323
|
+
expect(dist.files).toMatchInlineSnapshot(`
|
|
324
|
+
[
|
|
325
|
+
"index.d.ts",
|
|
326
|
+
"index.js",
|
|
327
|
+
"index.js.map",
|
|
328
|
+
"popup-CytznrQX.d.ts",
|
|
329
|
+
"popup-CytznrQX.d.ts.map",
|
|
330
|
+
"service.d.ts",
|
|
331
|
+
"service.d.ts.map",
|
|
332
|
+
"service.js",
|
|
333
|
+
"service.js.map",
|
|
334
|
+
"style.css",
|
|
335
|
+
]
|
|
336
|
+
`);
|
|
159
337
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
338
|
+
expect(printed(dist)).toMatchInlineSnapshot(`
|
|
339
|
+
"//=== index.d.ts
|
|
340
|
+
import { t as Popup } from "./popup-CytznrQX.js";
|
|
341
|
+
export { Popup };
|
|
342
|
+
//=== popup-CytznrQX.d.ts
|
|
343
|
+
import Component from "@glimmer/component";
|
|
344
|
+
//#region src/popup.d.ts
|
|
345
|
+
interface PopupBlock {
|
|
346
|
+
open: () => void;
|
|
347
|
+
}
|
|
348
|
+
declare class Popup extends Component {}
|
|
349
|
+
//#endregion
|
|
350
|
+
export { PopupBlock as n, Popup as t };
|
|
351
|
+
//# sourceMappingURL=popup-CytznrQX.d.ts.map
|
|
352
|
+
//=== service.d.ts
|
|
353
|
+
import { n as PopupBlock } from "./popup-CytznrQX.js";
|
|
354
|
+
//#region src/service.d.ts
|
|
355
|
+
declare function register(context: PopupBlock): void;
|
|
356
|
+
//#endregion
|
|
357
|
+
export { register };
|
|
358
|
+
//# sourceMappingURL=service.d.ts.map
|
|
359
|
+
//=== style.css
|
|
360
|
+
.popup {
|
|
361
|
+
color: red;
|
|
362
|
+
}
|
|
363
|
+
"
|
|
364
|
+
`);
|
|
165
365
|
});
|
|
166
366
|
});
|
|
@@ -170,4 +170,39 @@ describe("emberTransform (full plugin via rolldown)", () => {
|
|
|
170
170
|
"
|
|
171
171
|
`);
|
|
172
172
|
});
|
|
173
|
+
|
|
174
|
+
it("emits sourcemaps for the specifier rewrite (no SOURCEMAP_BROKEN warnings)", async () => {
|
|
175
|
+
const dir = await mkdtemp(path.join(tmpdir(), "ember-rolldown-map-"));
|
|
176
|
+
|
|
177
|
+
const files = {
|
|
178
|
+
// index.ts contains a `.gts` specifier, so it goes through the rewrite.
|
|
179
|
+
"index.ts": `export { default as Foo } from './foo.gts';`,
|
|
180
|
+
"foo.gts": `<template>Foo</template>`,
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
for (const [relative, source] of Object.entries(files)) {
|
|
184
|
+
await writeFile(path.join(dir, relative), source, "utf8");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const warnings: string[] = [];
|
|
188
|
+
const build = await rolldown({
|
|
189
|
+
input: path.join(dir, "index.ts"),
|
|
190
|
+
plugins: [emberTransform()],
|
|
191
|
+
external: (id) => !(id.startsWith(".") || path.isAbsolute(id)),
|
|
192
|
+
onwarn(warning) {
|
|
193
|
+
warnings.push(warning.code ?? String(warning));
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const { output } = await build.generate({ format: "es", sourcemap: true });
|
|
198
|
+
|
|
199
|
+
expect(warnings.filter((code) => code === "SOURCEMAP_BROKEN")).toEqual([]);
|
|
200
|
+
|
|
201
|
+
const chunk = output.find((entry) => entry.type === "chunk" && "map" in entry);
|
|
202
|
+
expect(chunk).toBeDefined();
|
|
203
|
+
// The composed map traces through the rewrite back to the original .gts
|
|
204
|
+
// source (content-tag's map survives the specifier-rewrite pass).
|
|
205
|
+
const sources = (chunk as { map?: { sources: string[] } }).map?.sources ?? [];
|
|
206
|
+
expect(sources.some((source) => source.endsWith("foo.gts"))).toBe(true);
|
|
207
|
+
});
|
|
173
208
|
});
|
|
@@ -38,13 +38,32 @@ async function fixDtsExtensionsInDir(dir: string): Promise<void> {
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* A sourcemap mapping every generated line to the same line of the input.
|
|
43
|
+
*
|
|
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.
|
|
48
|
+
*/
|
|
49
|
+
function lineIdentityMap(id: string, source: string) {
|
|
50
|
+
return {
|
|
51
|
+
version: 3,
|
|
52
|
+
sources: [id],
|
|
53
|
+
sourcesContent: [source],
|
|
54
|
+
names: [],
|
|
55
|
+
mappings: source
|
|
56
|
+
.split("\n")
|
|
57
|
+
.map((_, line) => (line === 0 ? "AAAA" : "AACA"))
|
|
58
|
+
.join(";"),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
41
62
|
/**
|
|
42
63
|
* Maps `.gts` -> `.ts` and `.gjs` -> `.js` (so rolldown can identify them as
|
|
43
64
|
* ts/js) and preprocesses `<template>` via content-tag.
|
|
44
65
|
*/
|
|
45
66
|
export function emberTransform(): Plugin {
|
|
46
|
-
const virtualFiles = new Set<string>();
|
|
47
|
-
|
|
48
67
|
return {
|
|
49
68
|
name: "ember:transform",
|
|
50
69
|
|
|
@@ -55,41 +74,39 @@ export function emberTransform(): Plugin {
|
|
|
55
74
|
|
|
56
75
|
const fileName = path.join(path.dirname(importer), id);
|
|
57
76
|
|
|
58
|
-
if (id.endsWith(".gts")
|
|
59
|
-
virtualFiles.add(fileName);
|
|
77
|
+
if (id.endsWith(".gts")) {
|
|
60
78
|
return {
|
|
61
79
|
id: fileName.replace(/\.gts$/, ".ts"),
|
|
62
80
|
meta: { fileName },
|
|
63
81
|
};
|
|
64
82
|
}
|
|
65
83
|
|
|
66
|
-
if (id.endsWith(".gjs")
|
|
67
|
-
virtualFiles.add(fileName);
|
|
84
|
+
if (id.endsWith(".gjs")) {
|
|
68
85
|
return {
|
|
69
86
|
id: fileName.replace(/\.gjs$/, ".js"),
|
|
70
87
|
meta: { fileName },
|
|
71
88
|
};
|
|
72
89
|
}
|
|
73
90
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
91
|
+
// A `.ts`/`.js` specifier whose only backing file is a `.gts`/`.gjs`
|
|
92
|
+
// resolves to the virtual module. This must also serve `.d.ts`
|
|
93
|
+
// importers: rolldown-plugin-dts resolves a declaration module's
|
|
94
|
+
// imports through the plugin pipeline (`this.resolve`), and — when the
|
|
95
|
+
// resolution is a source file — loads it (registering its declaration)
|
|
96
|
+
// before mapping the import to the declaration id. Virtual modules
|
|
97
|
+
// exist nowhere else, so if we don't answer here the import either
|
|
98
|
+
// fails to resolve (no file on disk) or, worse, resolves to a
|
|
99
|
+
// declaration id that was never registered.
|
|
100
|
+
if (id.endsWith(".ts")) {
|
|
82
101
|
const gtsFileName = fileName.replace(/\.ts$/, ".gts");
|
|
83
102
|
if (existsSync(gtsFileName) && !existsSync(fileName)) {
|
|
84
|
-
virtualFiles.add(gtsFileName);
|
|
85
103
|
return { id: fileName, meta: { fileName: gtsFileName } };
|
|
86
104
|
}
|
|
87
105
|
}
|
|
88
106
|
|
|
89
|
-
if (id.endsWith(".js")
|
|
107
|
+
if (id.endsWith(".js")) {
|
|
90
108
|
const gjsFileName = fileName.replace(/\.js$/, ".gjs");
|
|
91
109
|
if (existsSync(gjsFileName) && !existsSync(fileName)) {
|
|
92
|
-
virtualFiles.add(gjsFileName);
|
|
93
110
|
return { id: fileName, meta: { fileName: gjsFileName } };
|
|
94
111
|
}
|
|
95
112
|
}
|
|
@@ -105,7 +122,18 @@ export function emberTransform(): Plugin {
|
|
|
105
122
|
},
|
|
106
123
|
async handler(id) {
|
|
107
124
|
const meta = this.getModuleInfo(id)?.meta ?? {};
|
|
108
|
-
|
|
125
|
+
let fileName = meta?.fileName;
|
|
126
|
+
|
|
127
|
+
// A virtual id can be loaded without having passed through our
|
|
128
|
+
// resolveId (rolldown-plugin-dts calls `this.load({ id })` with just
|
|
129
|
+
// the id), so no meta is attached: recover the backing file from disk.
|
|
130
|
+
if (!fileName && !existsSync(id)) {
|
|
131
|
+
if (id.endsWith(".ts") && existsSync(id.replace(/\.ts$/, ".gts"))) {
|
|
132
|
+
fileName = id.replace(/\.ts$/, ".gts");
|
|
133
|
+
} else if (id.endsWith(".js") && existsSync(id.replace(/\.js$/, ".gjs"))) {
|
|
134
|
+
fileName = id.replace(/\.js$/, ".gjs");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
109
137
|
|
|
110
138
|
if (fileName) {
|
|
111
139
|
this.addWatchFile(fileName);
|
|
@@ -132,21 +160,26 @@ export function emberTransform(): Plugin {
|
|
|
132
160
|
id: /\.(js|ts)$/,
|
|
133
161
|
},
|
|
134
162
|
handler(input, id) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
163
|
+
// Rewrite `.gts` specifiers to `.ts` — the (virtual) source id — in
|
|
164
|
+
// runtime modules AND declaration modules alike. Declaration modules
|
|
165
|
+
// must NOT be rewritten to `.d.ts`: rolldown-plugin-dts's resolver
|
|
166
|
+
// treats a source-file resolution as "load it (registering its
|
|
167
|
+
// declaration), then map to the declaration id", while a `.d.ts` id is
|
|
168
|
+
// returned as-is — unloadable when the module isn't registered yet
|
|
169
|
+
// (e.g. the `.gts` is only ever type-imported).
|
|
170
|
+
const output = input.replace(
|
|
171
|
+
/(['"`])((?:\.\.?\/|\/|@|[A-Za-z0-9_\-])[^'"]*?\.gts)\1/g,
|
|
172
|
+
(_m, q, p) => `${q}${p.replace(/\.gts$/, ".ts")}${q}`,
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
if (output === input) {
|
|
176
|
+
return null;
|
|
147
177
|
}
|
|
148
178
|
|
|
149
|
-
|
|
179
|
+
// The rewrite only shortens import specifiers in place, so a
|
|
180
|
+
// line-identity map is accurate to the line (and to the column for
|
|
181
|
+
// everything before the first rewritten specifier on a line).
|
|
182
|
+
return { code: output, map: lineIdentityMap(id, input) };
|
|
150
183
|
},
|
|
151
184
|
},
|
|
152
185
|
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { describe, it, beforeAll, afterAll, expect } from "vitest";
|
|
2
|
+
import { generate, listFiles } from "#test-helpers";
|
|
3
|
+
import { execa } from "execa";
|
|
4
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
|
|
7
|
+
import type { Project } from "ember.nvp";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Integration test for building a library that uses ember-scoped-css through
|
|
11
|
+
* `@nullvoxpopuli/ember-rolldown` (tsdown):
|
|
12
|
+
*
|
|
13
|
+
* - the `scopedCSS()` unplugin (ember-scoped-css/rollup) resolves and rewrites
|
|
14
|
+
* the `?scoped=` CSS requests the template transform injects
|
|
15
|
+
* - the template transform + babel plugin ride along via `ember()`'s
|
|
16
|
+
* `babel.templateTransforms` / `babel.plugins` options
|
|
17
|
+
* - `@tsdown/css` with `css.inject` bundles the scoped CSS into
|
|
18
|
+
* `dist/style.css` and keeps the import in `dist/index.js`, so consuming
|
|
19
|
+
* apps load the styles through the module graph
|
|
20
|
+
*
|
|
21
|
+
* Covers all three authoring modes: co-located `.css`, inline
|
|
22
|
+
* `<style scoped>`, and the `scopedClass` pseudo-helper in module code.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const source = {
|
|
26
|
+
"tsdown.config.js": `import { defineConfig } from "tsdown";
|
|
27
|
+
import { ember } from "@nullvoxpopuli/ember-rolldown";
|
|
28
|
+
import { scopedCSS } from "ember-scoped-css/rollup";
|
|
29
|
+
import { scopedCSS as scopedCssBabel } from "ember-scoped-css/babel";
|
|
30
|
+
|
|
31
|
+
export default defineConfig({
|
|
32
|
+
entry: ["./src/index.ts"],
|
|
33
|
+
css: { inject: true },
|
|
34
|
+
plugins: [
|
|
35
|
+
ember({
|
|
36
|
+
babel: {
|
|
37
|
+
plugins: [scopedCssBabel()],
|
|
38
|
+
templateTransforms: [scopedCssBabel.template({})],
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
scopedCSS(),
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
`,
|
|
45
|
+
"src/index.ts": `export { Badge, type BadgeSignature } from "./components/badge.gts";
|
|
46
|
+
export { Banner, type BannerSignature } from "./components/banner.gts";
|
|
47
|
+
export { Chip, type ChipSignature, chipClass } from "./components/chip.gts";
|
|
48
|
+
`,
|
|
49
|
+
// co-located CSS
|
|
50
|
+
"src/components/badge.gts": `import type { TOC } from "@ember/component/template-only";
|
|
51
|
+
|
|
52
|
+
export interface BadgeSignature {
|
|
53
|
+
Element: HTMLSpanElement;
|
|
54
|
+
Blocks: {
|
|
55
|
+
default: [];
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const Badge: TOC<BadgeSignature> = <template>
|
|
60
|
+
<span class="badge" ...attributes>{{yield}}</span>
|
|
61
|
+
</template>;
|
|
62
|
+
`,
|
|
63
|
+
"src/components/badge.css": `.badge {
|
|
64
|
+
border-radius: 0.25rem;
|
|
65
|
+
padding: 0.25rem 0.5rem;
|
|
66
|
+
}
|
|
67
|
+
`,
|
|
68
|
+
// inline <style scoped>
|
|
69
|
+
"src/components/banner.gts": `import type { TOC } from "@ember/component/template-only";
|
|
70
|
+
|
|
71
|
+
export interface BannerSignature {
|
|
72
|
+
Element: HTMLDivElement;
|
|
73
|
+
Blocks: {
|
|
74
|
+
default: [];
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const Banner: TOC<BannerSignature> = <template>
|
|
79
|
+
<style scoped>
|
|
80
|
+
.banner {
|
|
81
|
+
border: 1px solid;
|
|
82
|
+
}
|
|
83
|
+
</style>
|
|
84
|
+
<div class="banner" ...attributes>{{yield}}</div>
|
|
85
|
+
</template>;
|
|
86
|
+
`,
|
|
87
|
+
// scopedClass pseudo-helper in module code
|
|
88
|
+
"src/components/chip.gts": `import type { TOC } from "@ember/component/template-only";
|
|
89
|
+
import { scopedClass } from "ember-scoped-css";
|
|
90
|
+
|
|
91
|
+
export const chipClass: string = scopedClass("chip");
|
|
92
|
+
|
|
93
|
+
export interface ChipSignature {
|
|
94
|
+
Element: HTMLSpanElement;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const Chip: TOC<ChipSignature> = <template>
|
|
98
|
+
<span class="chip" ...attributes></span>
|
|
99
|
+
</template>;
|
|
100
|
+
`,
|
|
101
|
+
"src/components/chip.css": `.chip {
|
|
102
|
+
display: inline-flex;
|
|
103
|
+
}
|
|
104
|
+
`,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
async function writeSource(project: Project) {
|
|
108
|
+
for (let [filePath, contents] of Object.entries(source)) {
|
|
109
|
+
let target = join(project.directory, filePath);
|
|
110
|
+
|
|
111
|
+
await mkdir(dirname(target), { recursive: true });
|
|
112
|
+
await writeFile(target, contents);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function addDependencies(project: Project) {
|
|
117
|
+
let manifestPath = join(project.directory, "package.json");
|
|
118
|
+
let manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
119
|
+
|
|
120
|
+
// @tsdown/css releases in lockstep with tsdown; both ranges track 0.22.x
|
|
121
|
+
manifest.devDependencies["@tsdown/css"] = "^0.22.13";
|
|
122
|
+
manifest.devDependencies["ember-scoped-css"] = "^3.1.0";
|
|
123
|
+
|
|
124
|
+
await writeFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
describe("library with ember-scoped-css", () => {
|
|
128
|
+
let project: Project;
|
|
129
|
+
|
|
130
|
+
beforeAll(async () => {
|
|
131
|
+
project = await generate({ type: "library", name: "scoped-lib", layers: ["typescript"] });
|
|
132
|
+
|
|
133
|
+
await writeSource(project);
|
|
134
|
+
await addDependencies(project);
|
|
135
|
+
|
|
136
|
+
let install = await execa("pnpm install", { cwd: project.directory, shell: true });
|
|
137
|
+
expect(install.exitCode).toBe(0);
|
|
138
|
+
|
|
139
|
+
let build = await execa("pnpm build", { cwd: project.directory, shell: true });
|
|
140
|
+
expect(build.exitCode).toBe(0);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
afterAll(async () => {
|
|
144
|
+
if (process.env.CI) return;
|
|
145
|
+
|
|
146
|
+
await rm(project.directory, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("emits the expected files", async () => {
|
|
150
|
+
expect(await listFiles(join(project.directory, "dist"))).toMatchInlineSnapshot(`
|
|
151
|
+
[
|
|
152
|
+
"index.d.ts",
|
|
153
|
+
"index.d.ts.map",
|
|
154
|
+
"index.js",
|
|
155
|
+
"index.js.map",
|
|
156
|
+
"style.css",
|
|
157
|
+
]
|
|
158
|
+
`);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("scopes classes in the precompiled templates and keeps the CSS import", async () => {
|
|
162
|
+
let output = await project.read("dist/index.js");
|
|
163
|
+
|
|
164
|
+
expect(output).toMatchInlineSnapshot(`
|
|
165
|
+
"import './style.css';
|
|
166
|
+
import { precompileTemplate } from "@ember/template-compilation";
|
|
167
|
+
import { setComponentTemplate } from "@ember/component";
|
|
168
|
+
import templateOnly from "@ember/component/template-only";
|
|
169
|
+
//#region src/components/badge.ts
|
|
170
|
+
const Badge = setComponentTemplate(precompileTemplate("<span class=\\"badge_e1025e732\\" ...attributes>{{yield}}</span>", { strictMode: true }), templateOnly());
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/components/banner.ts
|
|
173
|
+
const Banner = setComponentTemplate(precompileTemplate("\\n<div class=\\"banner_e88952290\\" ...attributes>{{yield}}</div>", { strictMode: true }), templateOnly());
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/components/chip.ts
|
|
176
|
+
const chipClass = "chip_ec34dd962";
|
|
177
|
+
const Chip = setComponentTemplate(precompileTemplate("<span class=\\"chip_ec34dd962\\" ...attributes></span>", { strictMode: true }), templateOnly());
|
|
178
|
+
//#endregion
|
|
179
|
+
export { Badge, Banner, Chip, chipClass };
|
|
180
|
+
|
|
181
|
+
//# sourceMappingURL=index.js.map"
|
|
182
|
+
`);
|
|
183
|
+
|
|
184
|
+
// the scopedClass import is compiled away; nothing of ember-scoped-css
|
|
185
|
+
// remains at runtime
|
|
186
|
+
expect(output).not.toContain("ember-scoped-css");
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("rewrites and bundles all three CSS sources", async () => {
|
|
190
|
+
expect(await project.read("dist/style.css")).toMatchInlineSnapshot(`
|
|
191
|
+
".badge_e1025e732 {
|
|
192
|
+
border-radius: .25rem;
|
|
193
|
+
padding: .25rem .5rem;
|
|
194
|
+
}
|
|
195
|
+
.banner_e88952290 {
|
|
196
|
+
border: 1px solid;
|
|
197
|
+
}
|
|
198
|
+
.chip_ec34dd962 {
|
|
199
|
+
display: inline-flex;
|
|
200
|
+
}
|
|
201
|
+
"
|
|
202
|
+
`);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("emits declarations untouched by scoping", async () => {
|
|
206
|
+
let declarations = await project.read("dist/index.d.ts");
|
|
207
|
+
|
|
208
|
+
expect(declarations).toContain("declare const Badge: TOC<BadgeSignature>");
|
|
209
|
+
expect(declarations).not.toContain("badge_");
|
|
210
|
+
});
|
|
211
|
+
});
|