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.
- package/.github/workflows/ci.yml +17 -2
- package/.release-plan.json +84 -4
- package/CHANGELOG.md +68 -0
- package/README.md +29 -0
- package/package.json +4 -2
- package/packages/build-tooling-utils/README.md +37 -0
- package/packages/build-tooling-utils/index.ts +2 -0
- package/packages/build-tooling-utils/package.json +42 -0
- package/packages/build-tooling-utils/rolldown.config.js +18 -0
- package/packages/build-tooling-utils/src/extensions.ts +18 -0
- package/packages/build-tooling-utils/src/maybe-babel.ts +146 -0
- package/packages/rolldown/README.md +115 -0
- package/packages/rolldown/index.ts +56 -0
- package/packages/rolldown/package.json +59 -0
- package/packages/rolldown/rolldown.config.js +21 -0
- package/packages/rolldown/src/babel.ts +136 -0
- package/packages/rolldown/src/define-config.ts +32 -0
- package/packages/rolldown/src/externals.ts +83 -0
- package/packages/rolldown/src/isolated-declarations.ts +65 -0
- package/packages/rolldown/src/transform.ts +162 -0
- package/packages/vite/README.md +7 -21
- package/packages/vite/index.ts +1 -1
- package/packages/vite/package.json +4 -6
- package/packages/vite/src/ember.ts +129 -15
- package/packages/vite/src/test-html.ts +86 -0
- package/src/bases/minimal-app/files/app/router.ts +1 -0
- package/src/bases/minimal-app/files/tsconfig.json +1 -0
- package/src/bases/minimal-app/index.js +2 -2
- package/src/bases/minimal-library/files/README.md +41 -0
- package/src/bases/minimal-library/files/package.json +49 -0
- package/src/bases/minimal-library/files/tsconfig.json +10 -0
- package/src/bases/minimal-library/files/tsdown.config.js +6 -0
- package/src/bases/minimal-library/index.js +158 -2
- package/src/cli/args.js +7 -1
- package/src/cli/index.js +90 -16
- package/src/cli/questions/layers.js +5 -2
- package/src/cli/questions/project-type.js +0 -5
- package/src/cli/questions/write.js +169 -0
- package/src/index.js +1 -0
- package/src/layers/are-the-types-wrong/index.js +74 -0
- package/src/layers/git/files/gitignore +14 -1
- package/src/layers/git/index.js +6 -5
- package/src/layers/github-actions/index.js +12 -8
- package/src/layers/index.js +1 -1
- package/src/layers/inspector-support/index.js +101 -0
- package/src/layers/prettier/files/.prettierignore +4 -0
- package/src/layers/publint/index.js +68 -0
- package/src/layers/qunit/files/tests/unit/macros-test.ts +2 -2
- package/src/layers/qunit/index.js +107 -7
- package/src/layers/qunit/library-files/config/test/babel.config.js +41 -0
- package/src/layers/qunit/library-files/config/test/testem.cjs +26 -0
- package/src/layers/qunit/library-files/tests/test-helper.ts +36 -0
- package/src/layers/qunit/library-files/tests/unit/.gitkeep +0 -0
- package/src/layers/qunit/library-files/vite.config.mjs +6 -0
- package/src/layers/typescript/index.js +43 -7
- package/src/layers/vitest/files/vitest.config.mjs +11 -11
- package/src/layers/vitest/index.js +70 -32
- package/src/utils/babel.js +9 -6
- package/src/utils/fs.js +44 -3
- package/src/utils/inspector-support.js +196 -0
- package/src/utils/npm.js +21 -7
- package/src/utils/remove-types.js +5 -0
- package/src/utils/rewrite-imports.js +157 -0
- package/src/utils/stage.js +317 -0
- package/src/utils/tsdown-config.js +85 -0
- package/src/utils/types.ts +5 -1
- package/test/ember.nvp/base-library.test.ts +232 -0
- package/test/ember.nvp/generator-replace-or-update.test.ts +2 -3
- package/test/ember.nvp/inspector-support.test.ts +179 -0
- package/test/ember.nvp/layer-inspector-support.test.ts +69 -0
- package/test/ember.nvp/layer-publish-checks.test.ts +103 -0
- package/test/ember.nvp/layer-qunit.test.ts +376 -15
- package/test/ember.nvp/layer-vitest.test.ts +250 -0
- package/test/ember.nvp/layers.test.ts +16 -0
- package/test/ember.nvp/library-src-fixtures.ts +81 -0
- package/test/ember.nvp/permutations/app-1.test.ts +3 -0
- package/test/ember.nvp/permutations/app-2.test.ts +3 -0
- package/test/ember.nvp/permutations/library-1.test.ts +3 -0
- package/test/ember.nvp/permutations/library-2.test.ts +3 -0
- package/test/ember.nvp/permutations/shared.ts +239 -0
- package/test/ember.nvp/rewrite-imports.test.ts +235 -0
- package/test/ember.nvp/stage.test.ts +188 -0
- package/test/test-helpers/permutate.test.ts +17 -0
- package/test/test-helpers.ts +20 -7
- package/tsconfig.json +1 -0
- package/packages/vite/src/maybe-babel.ts +0 -219
- package/src/layers/qunit/files/run-tests.mjs +0 -30
- package/src/layers/vitest/files/tests/test-helper.ts +0 -5
- package/test/ember.nvp/permutations.test.ts +0 -200
- /package/src/{layers/vitest/files/tests/integration/.gitkeep → bases/minimal-library/files/src/index.ts} +0 -0
- /package/src/layers/{vitest/files/tests/unit → qunit/library-files/tests/rendering}/.gitkeep +0 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { Preprocessor } from "content-tag";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { readFile, writeFile, readdir } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import type { Plugin } from "rolldown";
|
|
6
|
+
|
|
7
|
+
const processor = new Preprocessor();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `.d.ts` files emitted for `.gts` source reference the original `.gts`
|
|
11
|
+
* specifiers. Consumers can't resolve those, so we rewrite them back to
|
|
12
|
+
* extension-less specifiers.
|
|
13
|
+
*/
|
|
14
|
+
function fixDeclarationImports(content: string): string {
|
|
15
|
+
return content
|
|
16
|
+
.replace(/from\s+'([^']+)\.gts'/g, "from '$1'")
|
|
17
|
+
.replace(/from\s+"([^"]+)\.gts"/g, 'from "$1"')
|
|
18
|
+
.replace(/import\("([^"]+)\.gts"\)/g, 'import("$1")')
|
|
19
|
+
.replace(/import\('([^']+)\.gts'\)/g, "import('$1')");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function fixDtsExtensionsInDir(dir: string): Promise<void> {
|
|
23
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
24
|
+
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
const fullPath = path.join(dir, entry.name);
|
|
27
|
+
|
|
28
|
+
if (entry.isDirectory()) {
|
|
29
|
+
await fixDtsExtensionsInDir(fullPath);
|
|
30
|
+
} else if (entry.name.endsWith(".d.ts")) {
|
|
31
|
+
const content = await readFile(fullPath, { encoding: "utf8" });
|
|
32
|
+
const fixed = fixDeclarationImports(content);
|
|
33
|
+
|
|
34
|
+
if (fixed !== content) {
|
|
35
|
+
await writeFile(fullPath, fixed);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Maps `.gts` -> `.ts` and `.gjs` -> `.js` (so rolldown can identify them as
|
|
43
|
+
* ts/js) and preprocesses `<template>` via content-tag.
|
|
44
|
+
*/
|
|
45
|
+
export function emberTransform(): Plugin {
|
|
46
|
+
const virtualFiles = new Set<string>();
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
name: "ember:transform",
|
|
50
|
+
|
|
51
|
+
resolveId: {
|
|
52
|
+
order: "pre",
|
|
53
|
+
handler(id, importer) {
|
|
54
|
+
if (!importer) return null;
|
|
55
|
+
|
|
56
|
+
const fileName = path.join(path.dirname(importer), id);
|
|
57
|
+
|
|
58
|
+
if (id.endsWith(".gts") && !importer.endsWith(".d.ts")) {
|
|
59
|
+
virtualFiles.add(fileName);
|
|
60
|
+
return {
|
|
61
|
+
id: fileName.replace(/\.gts$/, ".ts"),
|
|
62
|
+
meta: { fileName },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (id.endsWith(".gjs") && !importer.endsWith(".d.ts")) {
|
|
67
|
+
virtualFiles.add(fileName);
|
|
68
|
+
return {
|
|
69
|
+
id: fileName.replace(/\.gjs$/, ".js"),
|
|
70
|
+
meta: { fileName },
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (id.endsWith(".d.ts") && importer.endsWith(".d.ts")) {
|
|
75
|
+
const gtsFile = fileName.replace(".d.ts", ".gts");
|
|
76
|
+
if (virtualFiles.has(gtsFile)) {
|
|
77
|
+
return fileName;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (id.endsWith(".ts") && !importer.endsWith(".d.ts")) {
|
|
82
|
+
const gtsFileName = fileName.replace(/\.ts$/, ".gts");
|
|
83
|
+
if (existsSync(gtsFileName) && !existsSync(fileName)) {
|
|
84
|
+
virtualFiles.add(gtsFileName);
|
|
85
|
+
return { id: fileName, meta: { fileName: gtsFileName } };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (id.endsWith(".js") && !importer.endsWith(".d.ts")) {
|
|
90
|
+
const gjsFileName = fileName.replace(/\.js$/, ".gjs");
|
|
91
|
+
if (existsSync(gjsFileName) && !existsSync(fileName)) {
|
|
92
|
+
virtualFiles.add(gjsFileName);
|
|
93
|
+
return { id: fileName, meta: { fileName: gjsFileName } };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return null;
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
load: {
|
|
102
|
+
order: "pre",
|
|
103
|
+
filter: {
|
|
104
|
+
id: /\.(ts|js)$/,
|
|
105
|
+
},
|
|
106
|
+
async handler(id) {
|
|
107
|
+
const meta = this.getModuleInfo(id)?.meta ?? {};
|
|
108
|
+
const fileName = meta?.fileName;
|
|
109
|
+
|
|
110
|
+
if (fileName) {
|
|
111
|
+
this.addWatchFile(fileName);
|
|
112
|
+
const source = await readFile(fileName, { encoding: "utf8" });
|
|
113
|
+
|
|
114
|
+
if (source.includes("<template>")) {
|
|
115
|
+
const { code, map } = processor.process(source, {
|
|
116
|
+
filename: fileName,
|
|
117
|
+
});
|
|
118
|
+
return { code, map };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return source;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return null;
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
transform: {
|
|
129
|
+
order: "pre",
|
|
130
|
+
filter: {
|
|
131
|
+
code: /\.gts/,
|
|
132
|
+
id: /\.(js|ts)$/,
|
|
133
|
+
},
|
|
134
|
+
handler(input, id) {
|
|
135
|
+
if (input.includes(".gts") && id.endsWith(".d.ts")) {
|
|
136
|
+
return input.replace(
|
|
137
|
+
/(['"`])((?:\.\.?\/|\/|@|[A-Za-z0-9_\-])[^'"]*?\.gts)\1/g,
|
|
138
|
+
(_m, q, p) => `${q}${p.replace(/\.gts$/, ".d.ts")}${q}`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (input.includes(".gts") && !id.endsWith(".d.ts")) {
|
|
143
|
+
return input.replace(
|
|
144
|
+
/(['"`])((?:\.\.?\/|\/|@|[A-Za-z0-9_\-])[^'"]*?\.gts)\1/g,
|
|
145
|
+
(_m, q, p) => `${q}${p.replace(/\.gts$/, ".ts")}${q}`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return input;
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
writeBundle: {
|
|
154
|
+
async handler(options) {
|
|
155
|
+
const outDir = (options as { dir?: string }).dir;
|
|
156
|
+
if (outDir) {
|
|
157
|
+
await fixDtsExtensionsInDir(path.resolve(outDir));
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
package/packages/vite/README.md
CHANGED
|
@@ -8,7 +8,9 @@ A speed-optimized default meta-config for ember projects.
|
|
|
8
8
|
npm add @nullvoxpopuli/ember-vite
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Requires node 24
|
|
11
|
+
Requires node 24+, and — since these packages ship TypeScript source — a
|
|
12
|
+
modern TypeScript when type-checking: 6+ with `lib` covering `es2025` (e.g.
|
|
13
|
+
`esnext`).
|
|
12
14
|
|
|
13
15
|
## Usage
|
|
14
16
|
|
|
@@ -43,34 +45,18 @@ The `ember()` plugin takes the following options:
|
|
|
43
45
|
|
|
44
46
|
```ts
|
|
45
47
|
ember({
|
|
46
|
-
// optional production configs
|
|
47
48
|
production: {
|
|
48
|
-
// optional configuration of codeSplitting.groups
|
|
49
|
-
// in the vite config.
|
|
50
|
-
//
|
|
51
|
-
// Docs here:
|
|
52
|
-
// https://rolldown.rs/reference/OutputOptions.codeSplitting#groups
|
|
53
49
|
codeSplittingGroups: [
|
|
54
|
-
/*
|
|
50
|
+
/* https://rolldown.rs/reference/OutputOptions.codeSplitting#groups */
|
|
55
51
|
],
|
|
56
52
|
},
|
|
57
|
-
// optional babel config
|
|
58
53
|
babel: {
|
|
59
|
-
// defaults to true.
|
|
60
|
-
// when babel runs, do so in parallel across many cores.
|
|
61
54
|
parallel: false,
|
|
62
|
-
// optional way to configure when babel is activateed.
|
|
63
|
-
// by default, all transforming is oxc, except when babel is needed
|
|
64
|
-
// (for things not currently implemented in oxc)
|
|
65
55
|
include: {
|
|
66
|
-
|
|
67
|
-
// oxc for that file.
|
|
68
|
-
//
|
|
69
|
-
// For example: ['ember-concurrency', 'ember-intl/format-message'],
|
|
70
|
-
whenImporting: [
|
|
71
|
-
/* ... */
|
|
72
|
-
],
|
|
56
|
+
whenImporting: ["ember-concurrency", "ember-intl/format-message"],
|
|
73
57
|
},
|
|
74
58
|
},
|
|
75
59
|
});
|
|
76
60
|
```
|
|
61
|
+
|
|
62
|
+
Each option is documented on its type.
|
package/packages/vite/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { ember } from "./src/ember.ts";
|
|
2
|
-
export { maybeBabel } from "
|
|
2
|
+
export { maybeBabel } from "@nullvoxpopuli/ember-build-tooling-utils";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nullvoxpopuli/ember-vite",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Meta plugin for ember projects focusing on speed.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -27,18 +27,16 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@babel/core": "^7.28.10",
|
|
29
29
|
"@embroider/core": "^4.4.2",
|
|
30
|
+
"@embroider/macros": "^1.20.3",
|
|
30
31
|
"@embroider/vite": "^1.5.0",
|
|
31
|
-
"@
|
|
32
|
-
"oxc-parser": "^0.132.0",
|
|
33
|
-
"oxc-transform": "^0.132.0",
|
|
34
|
-
"zimmerframe": "^1.1.4"
|
|
32
|
+
"@nullvoxpopuli/ember-build-tooling-utils": "workspace:*"
|
|
35
33
|
},
|
|
36
34
|
"engines": {
|
|
37
35
|
"node": ">= 24"
|
|
38
36
|
},
|
|
39
37
|
"devDependencies": {
|
|
40
38
|
"rolldown": "^1.1.2",
|
|
41
|
-
"typescript": "^
|
|
39
|
+
"typescript": "^6.0.3",
|
|
42
40
|
"vite": "^8.0.14"
|
|
43
41
|
}
|
|
44
42
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { buildMacros } from "@embroider/macros/babel";
|
|
1
2
|
import { ember as embroiderEmber, extensions, resolver, templateTag } from "@embroider/vite";
|
|
2
3
|
import { existsSync } from "node:fs";
|
|
3
4
|
import { join, resolve } from "node:path";
|
|
4
5
|
import type { ConfigEnv, ResolvedConfig } from "vite";
|
|
5
6
|
|
|
6
|
-
import { maybeBabel } from "
|
|
7
|
+
import { maybeBabel } from "@nullvoxpopuli/ember-build-tooling-utils";
|
|
8
|
+
|
|
9
|
+
import { testHtml } from "./test-html.ts";
|
|
7
10
|
|
|
8
11
|
const emberConfig = embroiderEmber()[3]!;
|
|
9
12
|
|
|
@@ -12,6 +15,92 @@ function absolutePath(relativePath: string) {
|
|
|
12
15
|
return join(cwd, relativePath);
|
|
13
16
|
}
|
|
14
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Everything a project without its own babel config needs:
|
|
20
|
+
*
|
|
21
|
+
* - TypeScript stripping
|
|
22
|
+
* - template compilation (wire format: vite serves/bundles for the
|
|
23
|
+
* project's own ember-source, so the final compile happens here)
|
|
24
|
+
* - build macros, evaluated at build time
|
|
25
|
+
* - decorator-transforms, with its runtime left as a bare specifier so it
|
|
26
|
+
* resolves from the project (which depends on `decorator-transforms`)
|
|
27
|
+
*
|
|
28
|
+
* The plugins are named, not imported: babel resolves them from the
|
|
29
|
+
* project, which carries them as its own dependencies.
|
|
30
|
+
*/
|
|
31
|
+
function defaultBabelPlugins() {
|
|
32
|
+
const macros = buildMacros();
|
|
33
|
+
|
|
34
|
+
return [
|
|
35
|
+
[
|
|
36
|
+
"@babel/plugin-transform-typescript",
|
|
37
|
+
{
|
|
38
|
+
allExtensions: true,
|
|
39
|
+
onlyRemoveTypeImports: true,
|
|
40
|
+
allowDeclareFields: true,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
["babel-plugin-ember-template-compilation", { transforms: [...macros.templateMacros] }],
|
|
44
|
+
["module:decorator-transforms", { runtime: { import: "decorator-transforms/runtime-esm" } }],
|
|
45
|
+
...macros.babelMacros,
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type BabelOptions =
|
|
50
|
+
| { configFile: string }
|
|
51
|
+
| {
|
|
52
|
+
configFile: false;
|
|
53
|
+
babelrc: false;
|
|
54
|
+
babelHelpers: "inline";
|
|
55
|
+
plugins: ReturnType<typeof defaultBabelPlugins>;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The project's babel config wins when it exists -- `babel.configFile`
|
|
60
|
+
* selects which one (e.g. a test-only `config/test/babel.config.js`).
|
|
61
|
+
* Without one (libraries), `defaultBabelPlugins` covers TypeScript,
|
|
62
|
+
* templates, macros, and decorators, so no config file is required --
|
|
63
|
+
* helpers are inlined because projects without a babel config don't
|
|
64
|
+
* carry `@babel/runtime`.
|
|
65
|
+
*/
|
|
66
|
+
function babelOptions(nvpConfig: Config): BabelOptions {
|
|
67
|
+
const configFile = resolve(
|
|
68
|
+
join(process.cwd(), nvpConfig.babel?.configFile ?? "./babel.config.js"),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (existsSync(configFile)) {
|
|
72
|
+
return { configFile };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
configFile: false,
|
|
77
|
+
babelrc: false,
|
|
78
|
+
babelHelpers: "inline",
|
|
79
|
+
plugins: defaultBabelPlugins(),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Additional opt-ins beyond maybeBabel's built-in list.
|
|
85
|
+
*
|
|
86
|
+
* These libraries ship code that still needs babel (ember-concurrency's
|
|
87
|
+
* decorators, ember-scoped-css transforms, ember-intl's formatMessage macro),
|
|
88
|
+
* and the `initializeRuntimeMacrosConfig` runtime call must be compiled away.
|
|
89
|
+
*/
|
|
90
|
+
function babelFilter(nvpConfig: Config) {
|
|
91
|
+
return {
|
|
92
|
+
include: {
|
|
93
|
+
imports: [
|
|
94
|
+
"ember-concurrency",
|
|
95
|
+
"ember-scoped-css",
|
|
96
|
+
"ember-intl/helpers/format-message",
|
|
97
|
+
...(nvpConfig.babel?.include?.whenImporting ?? []),
|
|
98
|
+
],
|
|
99
|
+
code: [/\bintl\.formatMessage\b/, "initializeRuntimeMacrosConfig"] as (string | RegExp)[],
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
15
104
|
interface Config {
|
|
16
105
|
babel?: {
|
|
17
106
|
/**
|
|
@@ -20,6 +109,16 @@ interface Config {
|
|
|
20
109
|
*/
|
|
21
110
|
parallel?: false;
|
|
22
111
|
|
|
112
|
+
/**
|
|
113
|
+
* which babel config file to use for this build, relative to the
|
|
114
|
+
* project root. defaults to "./babel.config.js".
|
|
115
|
+
*
|
|
116
|
+
* useful when the root babel config belongs to another pipeline
|
|
117
|
+
* (e.g. a library's publish build) and the vite build needs its own,
|
|
118
|
+
* such as "./config/test/babel.config.js".
|
|
119
|
+
*/
|
|
120
|
+
configFile?: string;
|
|
121
|
+
|
|
23
122
|
/**
|
|
24
123
|
* optional way to configure when babel is activateed.
|
|
25
124
|
* by default, all transforming is oxc, except when babel is needed
|
|
@@ -51,8 +150,8 @@ interface Config {
|
|
|
51
150
|
}
|
|
52
151
|
|
|
53
152
|
export function ember(nvpConfig: Config = {}) {
|
|
54
|
-
const
|
|
55
|
-
const
|
|
153
|
+
const babel = babelOptions(nvpConfig);
|
|
154
|
+
const filter = babelFilter(nvpConfig);
|
|
56
155
|
|
|
57
156
|
/*
|
|
58
157
|
* Plugins must be returned as top-level array entries — NOT pushed into
|
|
@@ -98,16 +197,21 @@ export function ember(nvpConfig: Config = {}) {
|
|
|
98
197
|
// Libraries will have precompileTemplate and macros, etc,
|
|
99
198
|
// and we need to compile that away using this app's
|
|
100
199
|
// template compiler
|
|
101
|
-
maybeBabel({
|
|
200
|
+
maybeBabel({
|
|
201
|
+
...babel,
|
|
202
|
+
parallel: nvpConfig.babel?.parallel,
|
|
203
|
+
filter,
|
|
204
|
+
}),
|
|
102
205
|
];
|
|
103
206
|
},
|
|
104
207
|
},
|
|
105
208
|
resolver({ rolldown: true }),
|
|
106
209
|
templateTag(),
|
|
210
|
+
testHtml(),
|
|
107
211
|
maybeBabel({
|
|
108
|
-
|
|
212
|
+
...babel,
|
|
109
213
|
parallel: nvpConfig.babel?.parallel,
|
|
110
|
-
|
|
214
|
+
filter,
|
|
111
215
|
}),
|
|
112
216
|
];
|
|
113
217
|
}
|
|
@@ -146,13 +250,10 @@ function dev(viteConfig: ResolvedConfig, nvpConfig: Config) {
|
|
|
146
250
|
viteConfig.build.rolldownOptions.input ||= {};
|
|
147
251
|
viteConfig.build.rolldownOptions.output ||= {};
|
|
148
252
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
if (existsSync("./tests.html")) {
|
|
253
|
+
// libraries don't, by default, have a "demo app" entrypoint
|
|
254
|
+
if (existsSync("./index.html")) {
|
|
154
255
|
Object.assign(viteConfig.build.rolldownOptions.input, {
|
|
155
|
-
|
|
256
|
+
main: absolutePath("./index.html"),
|
|
156
257
|
});
|
|
157
258
|
}
|
|
158
259
|
|
|
@@ -160,6 +261,16 @@ function dev(viteConfig: ResolvedConfig, nvpConfig: Config) {
|
|
|
160
261
|
Object.assign(viteConfig.build.rolldownOptions.input, {
|
|
161
262
|
tests: absolutePath("./tests/index.html"),
|
|
162
263
|
});
|
|
264
|
+
} else if (existsSync("./tests.html")) {
|
|
265
|
+
Object.assign(viteConfig.build.rolldownOptions.input, {
|
|
266
|
+
tests: absolutePath("./tests.html"),
|
|
267
|
+
});
|
|
268
|
+
} else if (existsSync("./tests")) {
|
|
269
|
+
// no html of its own: the default-test-html plugin answers for
|
|
270
|
+
// tests/index.html
|
|
271
|
+
Object.assign(viteConfig.build.rolldownOptions.input, {
|
|
272
|
+
tests: absolutePath("./tests/index.html"),
|
|
273
|
+
});
|
|
163
274
|
}
|
|
164
275
|
|
|
165
276
|
Object.assign(viteConfig.build.rolldownOptions.output, {
|
|
@@ -203,9 +314,12 @@ function prod(viteConfig: ResolvedConfig, nvpConfig: Config) {
|
|
|
203
314
|
viteConfig.build.rolldownOptions.optimization ||= {};
|
|
204
315
|
viteConfig.build.rolldownOptions.output ||= {};
|
|
205
316
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
317
|
+
// libraries don't, by default, have a "demo app" entrypoint
|
|
318
|
+
if (existsSync("./index.html")) {
|
|
319
|
+
Object.assign(viteConfig.build.rolldownOptions.input, {
|
|
320
|
+
main: absolutePath("./index.html"),
|
|
321
|
+
});
|
|
322
|
+
}
|
|
209
323
|
|
|
210
324
|
Object.assign(viteConfig.build.rolldownOptions.optimization, {
|
|
211
325
|
inlineConst: {
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { ViteDevServer } from "vite";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Everything a QUnit + testem run needs, with no project-specific
|
|
7
|
+
* references: root-relative specifiers keep it independent of where the
|
|
8
|
+
* project keeps its files, and the globs pick up tests/** as well as
|
|
9
|
+
* co-located `*-test` modules under src/**.
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_TEST_HTML = `<!doctype html>
|
|
12
|
+
<html>
|
|
13
|
+
<head>
|
|
14
|
+
<meta charset="utf-8" />
|
|
15
|
+
<title>Tests</title>
|
|
16
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
17
|
+
</head>
|
|
18
|
+
<body>
|
|
19
|
+
<div id="qunit"></div>
|
|
20
|
+
<div id="qunit-fixture">
|
|
21
|
+
<div id="ember-testing-container">
|
|
22
|
+
<div id="ember-testing"></div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<script src="/testem.js" integrity="" data-embroider-ignore></script>
|
|
27
|
+
<script type="module">
|
|
28
|
+
import "ember-testing";
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<script type="module">
|
|
32
|
+
import { start } from "/tests/test-helper";
|
|
33
|
+
import.meta.glob("/tests/**/*.{js,ts,gjs,gts}", { eager: true });
|
|
34
|
+
import.meta.glob("/src/**/*-test.{js,ts,gjs,gts}", { eager: true });
|
|
35
|
+
start();
|
|
36
|
+
</script>
|
|
37
|
+
</body>
|
|
38
|
+
</html>
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Lets a project run its tests without maintaining a tests/index.html:
|
|
43
|
+
* whenever that file is absent, this plugin answers for it -- `load`
|
|
44
|
+
* supplies the html to the build (so `tests/index.html` still lands in
|
|
45
|
+
* dist for testem), and the dev-server middleware serves it (through
|
|
46
|
+
* `transformIndexHtml`, so the html goes through the same pipeline as an
|
|
47
|
+
* on-disk file).
|
|
48
|
+
*
|
|
49
|
+
* A tests/index.html on disk always wins; every hook here defers to it.
|
|
50
|
+
*/
|
|
51
|
+
export function testHtml() {
|
|
52
|
+
const htmlPath = join(process.cwd(), "tests", "index.html");
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
name: "ember:default-test-html",
|
|
56
|
+
|
|
57
|
+
resolveId(id: string) {
|
|
58
|
+
if (id === htmlPath && !existsSync(htmlPath)) {
|
|
59
|
+
return id;
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
load(id: string) {
|
|
64
|
+
if (id === htmlPath && !existsSync(htmlPath)) {
|
|
65
|
+
return DEFAULT_TEST_HTML;
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
configureServer(server: ViteDevServer) {
|
|
70
|
+
server.middlewares.use(async (req, res, next) => {
|
|
71
|
+
if (existsSync(htmlPath)) return next();
|
|
72
|
+
|
|
73
|
+
const url = (req.url ?? "").split("?")[0];
|
|
74
|
+
|
|
75
|
+
if (url !== "/tests" && url !== "/tests/" && url !== "/tests/index.html") {
|
|
76
|
+
return next();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const html = await server.transformIndexHtml("/tests/index.html", DEFAULT_TEST_HTML);
|
|
80
|
+
|
|
81
|
+
res.setHeader("Content-Type", "text/html");
|
|
82
|
+
res.end(html);
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { packageJson, js } from "ember-apply";
|
|
2
|
+
import { join } from "node:path";
|
|
2
3
|
import { getLatest } from "#utils/npm.js";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { applyFolderTo } from "#utils/fs.js";
|
|
5
5
|
import { removeConfiguredPlugin } from "#utils/babel.js";
|
|
6
6
|
|
|
@@ -49,7 +49,7 @@ async function updateBabelConfig(project) {
|
|
|
49
49
|
* @param {import('#utils/project.js').Project} project
|
|
50
50
|
*/
|
|
51
51
|
async function applyFiles(project) {
|
|
52
|
-
let filePath =
|
|
52
|
+
let filePath = join(import.meta.dirname, "files");
|
|
53
53
|
|
|
54
54
|
await applyFolderTo(filePath, project);
|
|
55
55
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# minimal-library
|
|
2
|
+
|
|
3
|
+
An Ember v2 library (addon) built with
|
|
4
|
+
[`@nullvoxpopuli/ember-rolldown`](https://github.com/NullVoxPopuli/ember.nvp/tree/main/packages/rolldown)
|
|
5
|
+
and [tsdown](https://tsdown.dev/).
|
|
6
|
+
|
|
7
|
+
## Development
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Build the distributable (JS + `.d.ts` into `dist/`):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm build
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Rebuild on change while developing:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm start
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Structure
|
|
26
|
+
|
|
27
|
+
- `src/` — your library source. Author components in `.gts`/`.gjs`
|
|
28
|
+
(template-tag) and plain modules in `.ts`/`.js`.
|
|
29
|
+
- `src/index.ts` — the public entry point. Everything a consumer can import
|
|
30
|
+
must be re-exported from here (or added as an entry in `tsdown.config.js`).
|
|
31
|
+
- `dist/` — the built output that gets published (git-ignored).
|
|
32
|
+
|
|
33
|
+
Declarations are emitted with
|
|
34
|
+
[isolated declarations](https://www.typescriptlang.org/tsconfig/#isolatedDeclarations),
|
|
35
|
+
so every exported value needs an explicit type annotation — in particular,
|
|
36
|
+
annotate exported template-only components with `TOC<...>`.
|
|
37
|
+
|
|
38
|
+
## Publishing
|
|
39
|
+
|
|
40
|
+
`dist/` is built automatically on `prepack`, so `npm publish` ships the
|
|
41
|
+
compiled output plus your `src/`.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "minimal-library",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Your Ember library's description here",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./*": {
|
|
15
|
+
"types": "./dist/*.d.ts",
|
|
16
|
+
"default": "./dist/*.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"src"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsdown",
|
|
25
|
+
"prepack": "tsdown",
|
|
26
|
+
"start": "tsdown --watch"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"decorator-transforms": "^2.3.2"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@glimmer/component": "^2.0.0",
|
|
33
|
+
"ember-source": ">= 6.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@ember/library-tsconfig": "^2.0.0",
|
|
37
|
+
"@glimmer/component": "^2.1.1",
|
|
38
|
+
"@nullvoxpopuli/ember-rolldown": "workspace:*",
|
|
39
|
+
"ember-source": "~7.0.0",
|
|
40
|
+
"tsdown": "^0.22.7",
|
|
41
|
+
"typescript": "^6.0.3"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">= 24"
|
|
45
|
+
},
|
|
46
|
+
"ember": {
|
|
47
|
+
"edition": "octane"
|
|
48
|
+
}
|
|
49
|
+
}
|