ember.nvp 0.2.1 → 0.2.3

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.
@@ -2,21 +2,30 @@
2
2
  "solution": {
3
3
  "ember.nvp": {
4
4
  "impact": "patch",
5
- "oldVersion": "0.2.0",
6
- "newVersion": "0.2.1",
5
+ "oldVersion": "0.2.2",
6
+ "newVersion": "0.2.3",
7
7
  "tagName": "latest",
8
8
  "constraints": [
9
9
  {
10
10
  "impact": "patch",
11
11
  "reason": "Appears in changelog section :bug: Bug Fix"
12
- },
12
+ }
13
+ ],
14
+ "pkgJSONPath": "./package.json"
15
+ },
16
+ "@nullvoxpopuli/ember-vite": {
17
+ "impact": "patch",
18
+ "oldVersion": "1.0.1",
19
+ "newVersion": "1.0.2",
20
+ "tagName": "latest",
21
+ "constraints": [
13
22
  {
14
23
  "impact": "patch",
15
- "reason": "Appears in changelog section :house: Internal"
24
+ "reason": "Appears in changelog section :bug: Bug Fix"
16
25
  }
17
26
  ],
18
- "pkgJSONPath": "./package.json"
27
+ "pkgJSONPath": "./packages/vite/package.json"
19
28
  }
20
29
  },
21
- "description": "## Release (2026-05-15)\n\n* ember.nvp 0.2.1 (patch)\n\n#### :bug: Bug Fix\n* `ember.nvp`\n * [#29](https://github.com/NullVoxPopuli/ember.nvp/pull/29) Fix production vite build broken by setTesting in app/config.ts ([@Copilot](https://github.com/apps/copilot-swe-agent))\n\n#### :house: Internal\n* `ember.nvp`\n * [#31](https://github.com/NullVoxPopuli/ember.nvp/pull/31) Update release-plan ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### Committers: 2\n- Copilot [Bot] ([@copilot-swe-agent](https://github.com/apps/copilot-swe-agent))\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n"
30
+ "description": "## Release (2026-06-22)\n\n* ember.nvp 0.2.3 (patch)\n* @nullvoxpopuli/ember-vite 1.0.2 (patch)\n\n#### :bug: Bug Fix\n* `@nullvoxpopuli/ember-vite`, `ember.nvp`\n * [#41](https://github.com/NullVoxPopuli/ember.nvp/pull/41) Fix version range usage ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n* `ember.nvp`\n * [#40](https://github.com/NullVoxPopuli/ember.nvp/pull/40) Ensure imports exist ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### Committers: 1\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n"
22
31
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## Release (2026-06-22)
4
+
5
+ * ember.nvp 0.2.3 (patch)
6
+ * @nullvoxpopuli/ember-vite 1.0.2 (patch)
7
+
8
+ #### :bug: Bug Fix
9
+ * `@nullvoxpopuli/ember-vite`, `ember.nvp`
10
+ * [#41](https://github.com/NullVoxPopuli/ember.nvp/pull/41) Fix version range usage ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
11
+ * `ember.nvp`
12
+ * [#40](https://github.com/NullVoxPopuli/ember.nvp/pull/40) Ensure imports exist ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
13
+
14
+ #### Committers: 1
15
+ - [@NullVoxPopuli](https://github.com/NullVoxPopuli)
16
+
17
+ ## Release (2026-06-01)
18
+
19
+ * ember.nvp 0.2.2 (patch)
20
+ * @nullvoxpopuli/ember-vite 1.0.1 (patch)
21
+
22
+ #### :house: Internal
23
+ * `@nullvoxpopuli/ember-vite`
24
+ * [#36](https://github.com/NullVoxPopuli/ember.nvp/pull/36) Fix npm provenance failure for @nullvoxpopuli/ember-vite publish ([@Copilot](https://github.com/apps/copilot-swe-agent))
25
+ * `ember.nvp`, `@nullvoxpopuli/ember-vite`
26
+ * [#37](https://github.com/NullVoxPopuli/ember.nvp/pull/37) Revert "Prepare Release" ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
27
+
28
+ #### Committers: 2
29
+ - Copilot [Bot] ([@copilot-swe-agent](https://github.com/apps/copilot-swe-agent))
30
+ - [@NullVoxPopuli](https://github.com/NullVoxPopuli)
31
+
3
32
  ## Release (2026-05-15)
4
33
 
5
34
  * ember.nvp 0.2.1 (patch)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ember.nvp",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
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",
@@ -20,8 +20,8 @@
20
20
  "bin": "./src/cli/index.js",
21
21
  "dependencies": {
22
22
  "@clack/prompts": "^0.11.0",
23
- "babel-remove-types": "^1.1.0",
24
- "content-tag": "^4.1.0",
23
+ "babel-remove-types": "^2.0.0",
24
+ "content-tag": "^4.2.0",
25
25
  "ember-apply": "^2.17.0",
26
26
  "execa": "^9.6.1",
27
27
  "latest-version": "^9.0.0",
@@ -30,17 +30,17 @@
30
30
  "devDependencies": {
31
31
  "@ember/app-tsconfig": "^2.0.0",
32
32
  "@ember/library-tsconfig": "^2.0.0",
33
- "@glint/ember-tsc": "^1.0.9",
34
- "@glint/template": "^1.7.3",
35
- "@glint/tsserver-plugin": "^2.0.9",
33
+ "@glint/ember-tsc": "^1.7.5",
34
+ "@glint/template": "^1.7.7",
35
+ "@glint/tsserver-plugin": "^2.5.5",
36
36
  "concurrently": "^9.2.1",
37
- "ember-source": "^6.10.0",
38
- "prettier": "^3.7.4",
39
- "prettier-plugin-ember-template-tag": "^2.1.2",
37
+ "ember-source": "^7.0.0",
38
+ "prettier": "^3.8.3",
39
+ "prettier-plugin-ember-template-tag": "^2.1.6",
40
40
  "release-plan": "^0.18.0",
41
- "rimraf": "^6.1.2",
42
- "vite": "^7.3.1",
43
- "vitest": "^4.0.17"
41
+ "rimraf": "^6.1.3",
42
+ "vite": "^8.0.14",
43
+ "vitest": "^4.1.7"
44
44
  },
45
45
  "engines": {
46
46
  "node": ">= 24"
@@ -0,0 +1,76 @@
1
+ # `@nullvoxpopuli/ember-vite`
2
+
3
+ A speed-optimized default meta-config for ember projects.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm add @nullvoxpopuli/ember-vite
9
+ ```
10
+
11
+ Requires node 24+
12
+
13
+ ## Usage
14
+
15
+ in your vite config
16
+
17
+ ```js
18
+ import { defineConfig } from "vite";
19
+ import { ember } from "@nullvoxpopuli/ember-vite";
20
+
21
+ export default defineConfig({
22
+ plugins: [
23
+ ember(),
24
+ ],
25
+ }
26
+ ```
27
+
28
+ And remove any plugins from embroider or babel.
29
+ Then delete from your package.json: `@rollup/plugin-babel`, `@embroider/core`, `@embroider/vite`.
30
+
31
+ > [!NOTE]
32
+ > babel related deps are still needed for linting, so don't delete those
33
+
34
+ ## Requirements
35
+
36
+ - `type=module` in your ember app
37
+ - babel config must be named `babel.config.js`
38
+ - `@embroider/vite` is up to date~ish
39
+
40
+ ## Configuration
41
+
42
+ The `ember()` plugin takes the following options:
43
+
44
+ ```ts
45
+ ember({
46
+ // optional production configs
47
+ 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
+ codeSplittingGroups: [
54
+ /* ... */
55
+ ],
56
+ },
57
+ // optional babel config
58
+ babel: {
59
+ // defaults to true.
60
+ // when babel runs, do so in parallel across many cores.
61
+ 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
+ include: {
66
+ // if a file imports from anything in this array, use babel instead of
67
+ // oxc for that file.
68
+ //
69
+ // For example: ['ember-concurrency', 'ember-intl/format-message'],
70
+ whenImporting: [
71
+ /* ... */
72
+ ],
73
+ },
74
+ },
75
+ });
76
+ ```
@@ -0,0 +1,2 @@
1
+ export { ember } from "./src/ember.ts";
2
+ export { maybeBabel } from "./src/maybe-babel.ts";
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
19
+ else
20
+ exec node "$basedir/../typescript/bin/tsc" "$@"
21
+ fi
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
19
+ else
20
+ exec node "$basedir/../typescript/bin/tsserver" "$@"
21
+ fi
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/vite@8.0.14_@types+node@25.0.8_esbuild@0.27.2_jiti@2.6.1_terser@5.46.0/node_modules/vite/bin/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/vite@8.0.14_@types+node@25.0.8_esbuild@0.27.2_jiti@2.6.1_terser@5.46.0/node_modules/vite/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/vite@8.0.14_@types+node@25.0.8_esbuild@0.27.2_jiti@2.6.1_terser@5.46.0/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/vite@8.0.14_@types+node@25.0.8_esbuild@0.27.2_jiti@2.6.1_terser@5.46.0/node_modules/vite/bin/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/vite@8.0.14_@types+node@25.0.8_esbuild@0.27.2_jiti@2.6.1_terser@5.46.0/node_modules/vite/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/vite@8.0.14_@types+node@25.0.8_esbuild@0.27.2_jiti@2.6.1_terser@5.46.0/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../vite/bin/vite.js" "$@"
19
+ else
20
+ exec node "$basedir/../vite/bin/vite.js" "$@"
21
+ fi
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@nullvoxpopuli/ember-vite",
3
+ "version": "1.0.2",
4
+ "description": "Meta plugin for ember projects focusing on speed.",
5
+ "type": "module",
6
+ "main": "index.ts",
7
+ "keywords": [
8
+ "vite",
9
+ "ember",
10
+ "meta plugin"
11
+ ],
12
+ "author": "NullVoxPopuli",
13
+ "license": "MIT",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/NullVoxPopuli/ember.nvp",
17
+ "directory": "packages/vite"
18
+ },
19
+ "packageManager": "pnpm@10.28.0",
20
+ "dependencies": {
21
+ "@babel/core": "^7.28.10",
22
+ "@embroider/core": "^4.4.2",
23
+ "@embroider/vite": "^1.5.0",
24
+ "@rollup/plugin-babel": "^7.1.0",
25
+ "oxc-parser": "^0.132.0",
26
+ "oxc-transform": "^0.132.0",
27
+ "zimmerframe": "^1.1.4"
28
+ },
29
+ "engines": {
30
+ "node": ">= 24"
31
+ },
32
+ "devDependencies": {
33
+ "typescript": "^5.9.3",
34
+ "vite": "^8.0.14"
35
+ }
36
+ }
@@ -0,0 +1,239 @@
1
+ import { ember as embroiderEmber, extensions, resolver, templateTag } from "@embroider/vite";
2
+ import { existsSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import type { ConfigEnv, ResolvedConfig } from "vite";
5
+
6
+ import { maybeBabel } from "./maybe-babel.ts";
7
+
8
+ const emberConfig = embroiderEmber()[3]!;
9
+
10
+ const cwd = process.cwd();
11
+ function absolutePath(relativePath: string) {
12
+ return join(cwd, relativePath);
13
+ }
14
+
15
+ interface Config {
16
+ babel?: {
17
+ /**
18
+ * defaults to true.
19
+ * when babel runs, do so in parallel across many cores.
20
+ */
21
+ parallel?: false;
22
+
23
+ /**
24
+ * optional way to configure when babel is activateed.
25
+ * by default, all transforming is oxc, except when babel is needed
26
+ * (for things not currently implemented in oxc)
27
+ */
28
+ include?: {
29
+ /**
30
+ * if a file imports from anything in this array, use babel instead of
31
+ * oxc for that file.
32
+ *
33
+ * For example: ['ember-concurrency'],
34
+ */
35
+ whenImporting?: (string | "ember-concurrency" | "ember-scoped-css")[];
36
+ };
37
+ };
38
+ /**
39
+ * optional production configs
40
+ */
41
+ production?: {
42
+ /**
43
+ * optional configuration of codeSplitting.groups
44
+ * in the vite config.
45
+ *
46
+ * Docs here:
47
+ * https://rolldown.rs/reference/OutputOptions.codeSplitting#groups
48
+ */
49
+ codeSplittingGroups: any[];
50
+ };
51
+ }
52
+
53
+ export function ember(nvpConfig: Config = {}) {
54
+ const babelConfigFile = resolve(join(process.cwd(), "./babel.config.js"));
55
+ const defaultEnv = { mode: process.env.NODE_ENV || "development" };
56
+
57
+ /*
58
+ * Plugins must be returned as top-level array entries — NOT pushed into
59
+ * config.plugins inside a config() hook. Vite 8 extracts user plugins
60
+ * *before* config hooks run, so anything added later is invisible to the
61
+ * plugin container (resolveId, load, etc. hooks never fire).
62
+ */
63
+ return [
64
+ {
65
+ // no compat
66
+ name: "ember:nullvoxpopuli",
67
+ async config(viteConfig: any, env: ConfigEnv) {
68
+ // @ts-expect-error it exists
69
+ await emberConfig.config(viteConfig, env);
70
+
71
+ viteConfig.experimental ||= {};
72
+ viteConfig.build ||= {};
73
+ viteConfig.optimizeDeps ||= {};
74
+
75
+ viteConfig.oxc = {
76
+ // babel handles legacy decorators
77
+ // decorator: {
78
+ // legacy: true,
79
+ // },
80
+ };
81
+ // config.experimental.bundledDev = true;
82
+ viteConfig.experimental.nativeMagicString = true;
83
+ delete viteConfig.esbuild;
84
+ delete viteConfig.optimizeDeps.esbuildOptions;
85
+
86
+ viteConfig.build.reportCompressedSize = false;
87
+ viteConfig.build.extensions = extensions;
88
+ viteConfig.optimizeDeps.extensions = [".gjs", ".gts"];
89
+ viteConfig.optimizeDeps.rolldownOptions ||= {};
90
+ viteConfig.optimizeDeps.rolldownOptions.tsconfig = true;
91
+
92
+ applyConfig(viteConfig, env, nvpConfig);
93
+
94
+ viteConfig.optimizeDeps.rolldownOptions.plugins = [
95
+ resolver({ rolldown: true }),
96
+ templateTag(),
97
+
98
+ // Libraries will have precompileTemplate and macros, etc,
99
+ // and we need to compile that away using this app's
100
+ // template compiler
101
+ maybeBabel({ env, parallel: nvpConfig.babel?.parallel, configFile: babelConfigFile }),
102
+ ];
103
+ },
104
+ },
105
+ resolver({ rolldown: true }),
106
+ templateTag(),
107
+ maybeBabel({
108
+ env: defaultEnv,
109
+ parallel: nvpConfig.babel?.parallel,
110
+ configFile: babelConfigFile,
111
+ }),
112
+ ];
113
+ }
114
+
115
+ const MODE_DEV = "development";
116
+
117
+ function isDev(mode: string) {
118
+ return mode === MODE_DEV;
119
+ }
120
+
121
+ function applyConfig(viteConfig: ResolvedConfig, env: ConfigEnv, nvpConfig: Config) {
122
+ if (isDev(env.mode)) {
123
+ return dev(viteConfig, nvpConfig);
124
+ }
125
+
126
+ return prod(viteConfig, nvpConfig);
127
+ }
128
+
129
+ function dev(viteConfig: ResolvedConfig, nvpConfig: Config) {
130
+ /**************************************
131
+ *
132
+ * build config for tests
133
+ *
134
+ * NOTE: we can't minify because some tests
135
+ * are checking x.constructor.name, which changes when minified
136
+ *
137
+ *************************************/
138
+ Object.assign(viteConfig.build, {
139
+ sourcemap: true,
140
+ cleanCssOptions: { sourceMap: true },
141
+ minify: false,
142
+ cssMinify: false,
143
+ });
144
+
145
+ viteConfig.build.rolldownOptions ||= {};
146
+ viteConfig.build.rolldownOptions.input ||= {};
147
+ viteConfig.build.rolldownOptions.output ||= {};
148
+
149
+ Object.assign(viteConfig.build.rolldownOptions.input, {
150
+ main: absolutePath("./index.html"),
151
+ });
152
+
153
+ if (existsSync("./tests.html")) {
154
+ Object.assign(viteConfig.build.rolldownOptions.input, {
155
+ tests: absolutePath("./tests.html"),
156
+ });
157
+ }
158
+
159
+ if (existsSync("./tests/index.html")) {
160
+ Object.assign(viteConfig.build.rolldownOptions.input, {
161
+ tests: absolutePath("./tests/index.html"),
162
+ });
163
+ }
164
+
165
+ Object.assign(viteConfig.build.rolldownOptions.output, {
166
+ cleanDir: true,
167
+ strictExecutionOrder: true,
168
+ codeSplitting: {
169
+ groups: [
170
+ // our "test" product-land code happens to not match this pattern
171
+ { name: "tests", test: /[.-]test\.g?(j|t)s/, priority: 10 },
172
+ {
173
+ name: "common",
174
+ minShareCount: 10,
175
+ minSize: 10000,
176
+ maxSize: 1 * 1024 * 1024, // 1~ish MB
177
+ priority: 9,
178
+ },
179
+ {
180
+ name: "uncommon",
181
+ minShareCount: 2,
182
+ minSize: 10000,
183
+ maxSize: 1 * 1024 * 1024, // 1~ish MB
184
+ priority: 5,
185
+ },
186
+ ],
187
+ },
188
+ });
189
+
190
+ return;
191
+ }
192
+ function prod(viteConfig: ResolvedConfig, nvpConfig: Config) {
193
+ Object.assign(viteConfig.build, {
194
+ minify: "oxc",
195
+ sourcemap: true,
196
+ reportCompressedSize: false,
197
+ modulePreload: true,
198
+ cleanCssOptions: { sourceMap: true },
199
+ });
200
+
201
+ viteConfig.build.rolldownOptions ||= {};
202
+ viteConfig.build.rolldownOptions.input ||= {};
203
+ viteConfig.build.rolldownOptions.optimization ||= {};
204
+ viteConfig.build.rolldownOptions.output ||= {};
205
+
206
+ Object.assign(viteConfig.build.rolldownOptions.input, {
207
+ main: absolutePath("./index.html"),
208
+ });
209
+
210
+ Object.assign(viteConfig.build.rolldownOptions.optimization, {
211
+ inlineConst: {
212
+ mode: "smart",
213
+ pass: 3,
214
+ },
215
+ pifeForModuleWrappers: true,
216
+ });
217
+
218
+ Object.assign(viteConfig.build.rolldownOptions.output, {
219
+ minify: true,
220
+ cleanDir: true,
221
+ /**
222
+ * This is needed due to how we manage module state.
223
+ * This can create waterfalls when too much top-level await is used.
224
+ */
225
+ strictExecutionOrder: true,
226
+ /**
227
+ * Code splitting currently makes things worse
228
+ */
229
+ codeSplitting: {
230
+ /**
231
+ * Kind of a shame this isn't automatic based on dynamic import usage
232
+ * in vite8.
233
+ * Kinda forces us to know every potential problem area of the app,
234
+ * rather than rely on automatic optimizations based on import/module usage
235
+ */
236
+ groups: [...(nvpConfig?.production?.codeSplittingGroups ?? [])],
237
+ },
238
+ });
239
+ }
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Reasons we have to have babel
3
+ * - decorators
4
+ * - ember-concurrency
5
+ * - scoped-css
6
+ * - template compilation optimization
7
+ */
8
+ import { extensions } from "@embroider/vite";
9
+ import { babel } from "@rollup/plugin-babel";
10
+ import { join, resolve } from "node:path";
11
+ import { parse as oxcParse } from "oxc-parser";
12
+ import { transform } from "oxc-transform";
13
+ import { walk } from "zimmerframe";
14
+
15
+ /**
16
+ * if a file imports any of these, it must be optimize
17
+ */
18
+ const babelTemplateImports = new Set([
19
+ // Templates
20
+ "@ember/template-compiler",
21
+ "@ember/template-compilation",
22
+ "ember-cli-htmlbars",
23
+ "ember-cli-htmlbars-inline-precompile",
24
+ "htmlbars-inline-precompile",
25
+ ]);
26
+
27
+ const babelMacroImports = new Set([
28
+ // Macros
29
+ "@embroider/macros",
30
+ "@glimmer/env",
31
+ "@ember/application/deprecations",
32
+ // Macros only needed in prod
33
+ "@ember/debug",
34
+ ]); // Babel plugins required from libraries
35
+ const babelOtherImports = new Set([
36
+ "ember-concurrency",
37
+ "ember-scoped-css",
38
+ "ember-intl/helpers/format-message",
39
+ ]);
40
+
41
+ const babelRequiredImports = [...babelTemplateImports, ...babelMacroImports, ...babelOtherImports];
42
+
43
+ const counts = {
44
+ total: 0,
45
+ babel: 0,
46
+ why: {
47
+ decorators: 0,
48
+ formatMessage: 0,
49
+ imports: {
50
+ templates: 0,
51
+ macros: 0,
52
+ other: 0,
53
+ },
54
+ initializeRuntimeMacrosConfig: 0,
55
+ },
56
+ };
57
+ export function maybeBabel(options: { parallel?: boolean; configFile: string; env: any }) {
58
+ const { env, parallel, ...restOptions } = options;
59
+ const original = (() => {
60
+ const babelPath = resolve(join(process.cwd(), "./babel.config.js"));
61
+
62
+ return babel({
63
+ babelHelpers: "runtime",
64
+ extensions,
65
+ skipPreflightCheck: true,
66
+ ...restOptions,
67
+ configFile: babelPath,
68
+ parallel: parallel ?? true,
69
+ });
70
+ })();
71
+
72
+ /**
73
+ * In @rollup/plugin-babel v7+, the `transform` hook is the object form
74
+ * `{ filter, handler }` rather than a plain function. Normalize to the
75
+ * underlying handler so we can invoke it directly.
76
+ */
77
+ const originalTransform = (
78
+ typeof original.transform === "function" ? original.transform : original.transform?.handler
79
+ )!;
80
+
81
+ const babelMacros = new Set(babelRequiredImports);
82
+ if (env.mode === "development") {
83
+ babelMacros.delete("@ember/debug");
84
+ }
85
+
86
+ async function doTransform(this: any, code: string, id: string) {
87
+ counts.total++;
88
+
89
+ const ext = id.split(".").at(-1);
90
+ const lang = (ext === "gjs" ? "js" : ext === "gts" ? "ts" : ext) as
91
+ | "js"
92
+ | "ts"
93
+ | "jsx"
94
+ | "tsx"
95
+ | "dts"
96
+ | undefined;
97
+
98
+ let needsBabel = false;
99
+
100
+ const estree = await oxcParse(id, code, { lang });
101
+
102
+ walk(
103
+ estree.program as any,
104
+ /* state */ {},
105
+ {
106
+ Decorator(_node: any, { stop }: any) {
107
+ needsBabel = true;
108
+ counts.why.decorators++;
109
+ stop();
110
+ },
111
+ MemberExpression(node: any, { stop }: any) {
112
+ if (node.property?.name === "formatMessage" && node.object?.name === "intl") {
113
+ needsBabel = true;
114
+ counts.why.formatMessage++;
115
+ stop();
116
+ }
117
+ },
118
+ ImportDeclaration(node: any, { stop }: any) {
119
+ const value = node.source.value;
120
+ if (babelMacros.has(value)) {
121
+ needsBabel = true;
122
+
123
+ if (babelTemplateImports.has(value)) {
124
+ counts.why.imports.templates++;
125
+ } else if (babelMacroImports.has(value)) {
126
+ counts.why.imports.macros++;
127
+ } else {
128
+ counts.why.imports.other++;
129
+ }
130
+
131
+ stop();
132
+ }
133
+ },
134
+ },
135
+ );
136
+
137
+ if (!needsBabel && code.includes("initializeRuntimeMacrosConfig")) {
138
+ counts.why.initializeRuntimeMacrosConfig++;
139
+ needsBabel = true;
140
+ }
141
+
142
+ if (needsBabel) {
143
+ counts.babel++;
144
+
145
+ const result = await originalTransform.call(this, code, id);
146
+
147
+ return result;
148
+ }
149
+
150
+ if (ext === "json") {
151
+ return;
152
+ }
153
+
154
+ if (ext === "js" || ext === "gjs") {
155
+ // We don't need to process JS
156
+ return null;
157
+ }
158
+
159
+ const result = await transform(id, code, {
160
+ lang,
161
+ typescript: {
162
+ onlyRemoveTypeImports: true,
163
+ /**
164
+ * We should work towards disabling this.
165
+ */
166
+ allowNamespaces: true,
167
+ removeClassFieldsWithoutInitializer: false,
168
+ rewriteImportExtensions: false,
169
+ },
170
+ });
171
+
172
+ if (result.errors?.length) {
173
+ console.error(`Errors during oxc-transform of ${id}:`);
174
+ for (const err of result.errors) {
175
+ if (err.labels) {
176
+ console.error(err.labels);
177
+ }
178
+ console.error(err.codeframe || err.message);
179
+ }
180
+ throw result.errors;
181
+ }
182
+
183
+ return result;
184
+ }
185
+
186
+ return {
187
+ enforce: "pre",
188
+ buildEnd() {
189
+ if (process.env.INSPECT) {
190
+ console.debug(`Babel usage: ${counts.babel} / ${counts.total}`, counts.why);
191
+ }
192
+ },
193
+ ...original,
194
+ name: "nullvoxpopuli:babel",
195
+ transform: {
196
+ filter: {
197
+ id: {
198
+ include: [/\.js/, /\.gjs/, /\.ts/, /\.gts/],
199
+ exclude: [/\.json/],
200
+ },
201
+ // Enabling this opts us out of template compilation
202
+ // it's an AND instead of OR
203
+ // code: {
204
+ // include: [/initializeRuntimeMacrosConfig/, /precompileTemplate/],
205
+ // },
206
+ },
207
+ handler(this: any, code: string, id: string) {
208
+ try {
209
+ return doTransform.call(this, code, id);
210
+ } catch (e) {
211
+ // Use VSCode JavaScript Debug Terminal
212
+ // eslint-disable-next-line no-debugger
213
+ debugger;
214
+ throw e;
215
+ }
216
+ },
217
+ },
218
+ };
219
+ }
@@ -1,3 +1,4 @@
1
1
  packages:
2
2
  - .
3
3
  - src/bases/*/files
4
+ - packages/*
@@ -25,28 +25,28 @@
25
25
  "start": "vite"
26
26
  },
27
27
  "dependencies": {
28
- "@embroider/macros": "^1.19.6",
28
+ "@embroider/macros": "^1.20.3",
29
29
  "@embroider/router": "^3.0.6",
30
- "@glimmer/component": "^2.0.0",
31
- "decorator-transforms": "^2.3.1",
32
- "ember-source": "~6.10.0",
30
+ "@glimmer/component": "^2.1.1",
31
+ "decorator-transforms": "^2.3.2",
32
+ "ember-source": "~7.0.0",
33
33
  "ember-strict-application-resolver": "^0.1.0"
34
34
  },
35
35
  "devDependencies": {
36
- "@babel/core": "^7.28.5",
37
- "@babel/plugin-transform-runtime": "^7.28.5",
38
- "@babel/plugin-transform-typescript": "^7.28.5",
39
- "@babel/runtime": "^7.28.4",
36
+ "@babel/core": "^7.29.7",
37
+ "@babel/plugin-transform-runtime": "^7.29.7",
38
+ "@babel/plugin-transform-typescript": "^7.29.7",
39
+ "@babel/runtime": "^7.29.7",
40
40
  "@ember/app-tsconfig": "^2.0.0",
41
41
  "@embroider/core": "^4.4.2",
42
- "@embroider/vite": "^1.5.0",
43
- "@glint/ember-tsc": "^1.0.8",
44
- "@glint/template": "^1.7.3",
45
- "@glint/tsserver-plugin": "^2.0.8",
46
- "@rollup/plugin-babel": "^6.1.0",
42
+ "@glint/ember-tsc": "^1.7.5",
43
+ "@glint/template": "^1.7.7",
44
+ "@glint/tsserver-plugin": "^2.5.5",
45
+ "@nullvoxpopuli/ember-vite": "workspace:*",
46
+ "@rollup/plugin-babel": "^7.1.0",
47
47
  "babel-plugin-ember-template-compilation": "^4.0.0",
48
- "typescript": "^5.9.3",
49
- "vite": "^7.3.0"
48
+ "typescript": "^6.0.3",
49
+ "vite": "^8.0.14"
50
50
  },
51
51
  "engines": {
52
52
  "node": ">= 24"
@@ -1,13 +1,6 @@
1
1
  import { defineConfig } from "vite";
2
- import { extensions, ember } from "@embroider/vite";
3
- import { babel } from "@rollup/plugin-babel";
2
+ import { ember } from "@nullvoxpopuli/ember-vite";
4
3
 
5
4
  export default defineConfig({
6
- plugins: [
7
- ember(),
8
- babel({
9
- babelHelpers: "runtime",
10
- extensions,
11
- }),
12
- ],
5
+ plugins: [ember()],
13
6
  });
@@ -91,6 +91,7 @@ async function makeJavaScript(project) {
91
91
  await project.removeFile("tsconfig.json");
92
92
 
93
93
  await packageJson.modify((json) => {
94
+ json.imports ||= {};
94
95
  json.imports["#config"] = "./app/config.js";
95
96
  }, project.directory);
96
97
  }
@@ -1,6 +1,7 @@
1
- import * as p from "@clack/prompts";
1
+ import { files } from "ember-apply";
2
2
  import { hasGit, initGit, isInGit } from "#utils/git.js";
3
3
  import { formatLabel } from "#utils/cli.js";
4
+ import { join } from "node:path";
4
5
 
5
6
  export default {
6
7
  label: formatLabel("git init"),
@@ -21,7 +22,11 @@ export default {
21
22
  return;
22
23
  }
23
24
 
24
- return initGit(project.directory);
25
+ let initOk = initGit(project.directory);
26
+ if (initOk) {
27
+ await files.applyFolder(join(import.meta.dirname, "files"), project.directory);
28
+ }
29
+ return initOk;
25
30
  },
26
31
 
27
32
  /**
package/src/utils/npm.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import latestVersion from "latest-version";
2
+ import { existsSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
2
4
 
3
5
  /**
4
6
  * @type {{ [name: string]: { [version: string]: string } }}
@@ -9,14 +11,38 @@ const CACHE = {};
9
11
  * @param {{ [name: string]: string }} deps map of dep name to semver range
10
12
  */
11
13
  export async function getLatest(deps) {
14
+ let needsLocalLink = await needsWorkspace();
15
+
12
16
  let results = await Promise.all(
13
17
  Object.entries(deps).map(async ([dep, range]) => {
14
18
  let existing = CACHE[dep]?.[range];
19
+
15
20
  if (existing) {
16
21
  return [dep, existing];
17
22
  }
18
23
 
19
- let version = await latestVersion(dep, { version: range });
24
+ let version;
25
+
26
+ /**
27
+ * HACK FOR CI.
28
+ * In practice, this package will be published separately, and that version will be used.
29
+ * Only the local, not-yet-published @nullvoxpopuli/ember-vite package needs the link;
30
+ * every other dependency must still resolve its real version.
31
+ *
32
+ * We use `link:` (a symlink) rather than `file:` (a copy into the store)
33
+ * on purpose: the package ships TypeScript source, and Node 24 refuses to
34
+ * strip types for files physically located under node_modules. A symlink
35
+ * makes Node resolve the realpath to packages/vite (outside node_modules),
36
+ * so its `.ts` runs directly.
37
+ */
38
+ if (needsLocalLink && dep === "@nullvoxpopuli/ember-vite") {
39
+ version = "link:" + resolve(join(import.meta.dirname, "../../packages/vite"));
40
+ } else {
41
+ if (range == "workspace:*") {
42
+ range = "latest";
43
+ }
44
+ version = await latestVersion(dep, { version: range });
45
+ }
20
46
 
21
47
  CACHE[dep] ||= {};
22
48
  CACHE[dep][range] = version;
@@ -27,3 +53,15 @@ export async function getLatest(deps) {
27
53
 
28
54
  return Object.fromEntries(results);
29
55
  }
56
+
57
+ async function needsWorkspace() {
58
+ if (process.env.GITHUB_REPOSITORY === "NullVoxPopuli/ember.nvp") return true;
59
+
60
+ let root = resolve(import.meta.dirname, "../../");
61
+
62
+ if (existsSync(join(root, ".git"))) {
63
+ return true;
64
+ }
65
+
66
+ return false;
67
+ }
package/tsconfig.json CHANGED
@@ -8,6 +8,7 @@
8
8
  "exclude": [
9
9
  "node_modules",
10
10
  "dist",
11
+ "packages",
11
12
  "src/**/layers/*/files/**",
12
13
  "src/**/bases/*/files/**",
13
14
  "my-app"