|
| 1 | +--- |
| 2 | +stage: accepted |
| 3 | +start-date: 2023-11-02T16:05:11.000Z |
| 4 | +release-date: # In format YYYY-MM-DDT00:00:00.000Z |
| 5 | +release-versions: |
| 6 | +teams: # delete teams that aren't relevant |
| 7 | + - cli |
| 8 | + - data |
| 9 | + - framework |
| 10 | +prs: |
| 11 | + accepted: https://github.com/emberjs/rfcs/pull/985 |
| 12 | +project-link: |
| 13 | +suite: |
| 14 | +--- |
| 15 | + |
| 16 | +# Change the default addon blueprint to `@ember/addon-blueprint` |
| 17 | + |
| 18 | +## Summary |
| 19 | + |
| 20 | +This RFC proposes making [`@ember/addon-blueprint`](https://github.com/ember-cli/ember-addon-blueprint) the default blueprint for new Ember addons, replacing the current v1 and v2 blueprints. The existing blueprints present significant technical challenges that impact developer productivity and ecosystem compatibility. The new blueprint addresses these issues through modern tooling, streamlined architecture, and comprehensive TypeScript integration based on extensive community feedback and production usage patterns. |
| 21 | + |
| 22 | +## Motivation |
| 23 | + |
| 24 | +The current default v1 addon blueprint generates addons that get rebuilt by every consuming app, which is slow and couples addon builds to app builds. There is no built-in path to modern tooling like TypeScript, Glint, or Vite. The app blueprint already defaults to a modern Vite-based setup, so the addon blueprint is out of parity -- new addon authors get a significantly worse starting experience than new app authors. |
| 25 | + |
| 26 | +`@ember/addon-blueprint` already exists and has been widely adopted by the community. Making it the default gives new addon authors a working setup with single-package structure, Glint for template type safety, native classes and strict mode throughout, and sensible tooling defaults out of the box. |
| 27 | + |
| 28 | +Goals: |
| 29 | +- The new blueprint should : |
| 30 | + - emit browser-code by default (engines / node doesn't matter) |
| 31 | + - use standard ecosystem tooling with broad support |
| 32 | + - be familiar / easy to test / make demo apps with |
| 33 | + - document and explain how the npm packaging process works, and what concerns someone publishing a package needs to be aware of |
| 34 | + - have escape hatches / configuration for folks who don't need everything that a publisher would need |
| 35 | + - compose within monorepos |
| 36 | + |
| 37 | +> [!NOTE] |
| 38 | +> the point about publishing concerns could be the most contentious, as some folks feel we have too many config files. And while there are more than a v1 addon, they are _needed_ when your develop and publish environments/targets are different. More on that later. |
| 39 | +
|
| 40 | +## Detailed design |
| 41 | + |
| 42 | +### Definitions |
| 43 | + |
| 44 | +**V2 Addon**: An addon with `ember-addon.version: 2` in package.json, as defined by [RFC 507](https://rfcs.emberjs.com/id/0507-embroider-v2-package-format/). |
| 45 | + |
| 46 | +**Single-package addon**: An addon with its test suite in the same package, rather than a separate test app in a monorepo. |
| 47 | + |
| 48 | +**Blueprint**: A code generation template used by ember-cli to scaffold projects. |
| 49 | + |
| 50 | +### Migration |
| 51 | + |
| 52 | +The majority of this RFC is written from the perspective of someone that is running ember new for the first time on a brand new addon, but we will need to make sure to write both upgrade guides and appropriate codemods for anyone that is wanting to upgrade their addons from the old blueprints to this new default. We also need to put some consideration into the experience of people using `ember-cli-update` to upgrade when we make this the new default. |
| 53 | + |
| 54 | +#### `ember-cli-update` Support |
| 55 | + |
| 56 | +The blueprint includes `config/ember-cli-update.json` so that `ember-cli-update` continues to work. This file tracks the blueprint package name and version, allowing `ember-cli-update` to detect available updates and apply them. The entry should reference `@ember/addon-blueprint` and the version used to generate the addon, following the same pattern used by the app blueprint. |
| 57 | + |
| 58 | +Note that there will be a version boundary across which `ember-cli-update` cannot automatically migrate. Addons generated with the old v1 blueprint cannot be automatically updated to the new `@ember/addon-blueprint` via `ember-cli-update` -- the project structures are too different. Similar to how apps needed to reach a specific ember-cli version before `ember-cli-update` could bridge to the Vite-based app blueprint, addon authors will need to do a one-time manual migration (or use a codemod, once available) to get onto the new blueprint. Once on the new blueprint, `ember-cli-update` will work normally for subsequent updates. |
| 59 | + |
| 60 | +#### Codemod |
| 61 | + |
| 62 | +A codemod for migrating existing v1 addons to the new blueprint structure is out of scope for this RFC, but would be a valuable follow-up effort. [Mainmatter](https://mainmatter.com/) has expressed interest in developing such a codemod. In the meantime, addon authors can generate a fresh project with the new blueprint and manually move their source code into it. The [embroider-build/embroider](https://github.com/embroider-build/embroider) repo also has documentation on how to work with and migrate to v2 addons. |
| 63 | + |
| 64 | + |
| 65 | +### In-repo addons |
| 66 | + |
| 67 | +_Proper_ in-repo addons, are not going to be supported with this blueprint. |
| 68 | + |
| 69 | +`ember-cli` will throw an error _unless_ the user explicitly opts in to tho old blueprint. |
| 70 | + |
| 71 | +### Generators |
| 72 | + |
| 73 | +The new blueprint does not include `ember-cli` as a dependency. This means commands like `ember generate component foo` will not work out of the box in a v2 addon: |
| 74 | + |
| 75 | +``` |
| 76 | +# pnpm dlx ember-cli g component foo |
| 77 | +
|
| 78 | +You have to be inside an ember-cli project to use the generate command. |
| 79 | +``` |
| 80 | + |
| 81 | +_v2 addons are not ember-cli projects_. |
| 82 | + |
| 83 | +> [!IMPORTANT] |
| 84 | +> This is expected behavior, however before we mark this "v2 addon by default" project as "ready for release", we'll want _some way_ to help folks generate files. |
| 85 | +
|
| 86 | + |
| 87 | + |
| 88 | +## How we teach this |
| 89 | + |
| 90 | +### Documentation Updates |
| 91 | + |
| 92 | +- Update the Ember Guides to reference the new blueprint |
| 93 | +- The blueprint README covers customization, publishing, and multi-version support |
| 94 | +- Provide migration guides for v1 and v2 addon authors |
| 95 | +- The blueprint should generate parallel `.md` files (or inline comments) alongside config files to explain the purpose and rationale of each configuration. This helps addon authors understand *why* a config exists, not just *what* it contains, and reduces confusion when configs change across blueprint versions |
| 96 | +- Review the existing Ember Guides to identify workflows that won't work with the new blueprint and document alternatives |
| 97 | + |
| 98 | +### Key Concepts for Addon Authors |
| 99 | + |
| 100 | +#### `exports` and `imports` |
| 101 | + |
| 102 | +> [!NOTE] |
| 103 | +> This may look more complicated than v1 addons, but this is what the wider ecosystem is doing, and a lot of tooling knows to look at these package.json configs, and we'll benefit from that tooling. |
| 104 | +
|
| 105 | +`exports` defines your addon's public API: |
| 106 | + |
| 107 | +```json |
| 108 | +{ |
| 109 | + "exports": { |
| 110 | + ".": { |
| 111 | + "types": "./declarations/index.d.ts", |
| 112 | + "default": "./dist/index.js" |
| 113 | + }, |
| 114 | + "./*.css": "./dist/*.css", |
| 115 | + "./*": { |
| 116 | + "types": "./declarations/*.d.ts", |
| 117 | + "default": "./dist/*.js" |
| 118 | + } |
| 119 | + } |
| 120 | +} |
| 121 | +``` |
| 122 | + |
| 123 | +`imports` with `#src/*` lets tests and the demo app import from source without rebuilding. Can't be used in `src/` (Rollup won't transform these). Files in `src/` must use relative imports. |
| 124 | + |
| 125 | +#### Importing Addon Code in Tests: `#src/*` vs. Consumer-Style |
| 126 | + |
| 127 | +When writing tests, you have two ways to import from your addon: |
| 128 | + |
| 129 | +**`#src/*` imports** -- import directly from source files: |
| 130 | +```javascript |
| 131 | +import { myHelper } from '#src/helpers/my-helper'; |
| 132 | +``` |
| 133 | +- Works immediately, no build step needed |
| 134 | +- Fast feedback loop during development |
| 135 | +- Tests the source code directly |
| 136 | + |
| 137 | +**Consumer-style imports** -- import as a consumer would: |
| 138 | +```javascript |
| 139 | +import { myHelper } from 'my-addon/helpers/my-helper'; |
| 140 | +``` |
| 141 | +- Tests the published API surface |
| 142 | +- Requires `dist/` to exist (needs a build first) |
| 143 | +- Catches issues with `exports` mapping or build transforms |
| 144 | + |
| 145 | +#### Self-Imports |
| 146 | + |
| 147 | +Self-imports (e.g. `import { x } from 'my-addon/foo'`) don't work during development in `src/` files because they resolve through `exports` to `dist/`, which doesn't exist until you build. Use relative imports in `src/`: |
| 148 | + |
| 149 | +```javascript |
| 150 | +// In src/ files: |
| 151 | +import { myHelper } from './helpers/my-helper'; // yes |
| 152 | +import { myHelper } from 'my-addon/helpers/my-helper'; // no |
| 153 | +``` |
| 154 | + |
| 155 | +> [!NOTE] |
| 156 | +> self-imports _can_ work with custom export conditions coordinated between package.json, rollup, and vite configs. |
| 157 | +
|
| 158 | +#### Dev vs. Publish Configs |
| 159 | + |
| 160 | +| Purpose | Dev | Publish | |
| 161 | +|---------|-----|---------| |
| 162 | +| Babel | `babel.config.cjs` (macros, compat) | `babel.publish.config.cjs` (minimal) | |
| 163 | +| TypeScript | `tsconfig.json` (all files, Vite types) | `tsconfig.publish.json` (src only) | |
| 164 | +| Build | `vite.config.mjs` (HMR, tests) | `rollup.config.mjs` (tree-shaking) | |
| 165 | + |
| 166 | +Macros are evaluated in dev for testing but left unevaluated in published output -- the consuming app handles them. The publish tsconfig omits Vite/Embroider types so `lint:types` catches accidental usage. |
| 167 | + |
| 168 | +#### Monorepo Setup |
| 169 | + |
| 170 | + |
| 171 | +The single-package default works for most addons. If you need a monorepo (complex integration testing, multiple related packages, full documentation app): |
| 172 | + |
| 173 | +> [!NOTE] |
| 174 | +> This is an example monorepo setup, and may differ from what your monorepo. Some addons may want _multiple_ test-apps for various configurations / build setups. |
| 175 | +
|
| 176 | +1. Generate addon: `npx ember-cli@latest addon my-addon --blueprint @ember/addon-blueprint --skip-git` |
| 177 | +2. Generate test app: `npx ember-cli@latest app test-app --blueprint @ember/app-blueprint` |
| 178 | +3. Set up workspace tooling (pnpm workspaces) |
| 179 | +4. Install addon in test app |
| 180 | + |
| 181 | +#### Unpublished Addons in a Monorepo |
| 182 | + |
| 183 | +Sometimes you have a v2 addon in a monorepo that's only consumed by other packages in the workspace -- it's never published to npm. In this case you can skip the build step entirely and point `exports` at your source files: |
| 184 | + |
| 185 | +```json |
| 186 | +{ |
| 187 | + "name": "my-internal-addon", |
| 188 | + "ember-addon": { |
| 189 | + "version": 2, |
| 190 | + "type": "addon", |
| 191 | + "main": "addon-main.cjs" |
| 192 | + }, |
| 193 | + "exports": { |
| 194 | + ".": { |
| 195 | + "types": "./src/index.ts", |
| 196 | + "default": "./src/index.ts" |
| 197 | + }, |
| 198 | + "./*": { |
| 199 | + "types": "./src/*", |
| 200 | + "default": "./src/*" |
| 201 | + } |
| 202 | + } |
| 203 | +} |
| 204 | +``` |
| 205 | + |
| 206 | +Key differences from a published addon: |
| 207 | +- `exports` points to `src/` instead of `dist/` and `declarations/` |
| 208 | +- No `files` array needed (not publishing to npm) |
| 209 | +- No rollup build, no `prepack` script, no `declarations/` directory |
| 210 | +- No `babel.publish.config.cjs` or `tsconfig.publish.json` needed |
| 211 | +- You still need `addon-main.cjs` if any consuming app in the workspace uses the classic ember-cli build |
| 212 | + |
| 213 | +The consuming app's build tooling (Vite/Embroider) handles the transpilation. This is much simpler to maintain for workspace-internal code. |
| 214 | + |
| 215 | +#### In-Repo Addons |
| 216 | + |
| 217 | +Classic in-repo addons (the `lib/` directory pattern) are v1 constructs. To create the v2 equivalent, use your package manager's workspace features to establish them as real package dependencies: |
| 218 | + |
| 219 | +1. Create a directory for the addon (e.g. `packages/my-internal-addon/` or keep `lib/my-internal-addon/`) |
| 220 | +2. Give it a `package.json` with `ember-addon.version: 2` |
| 221 | +3. Add it to your workspace configuration (e.g. pnpm `workspace.yaml` or `package.json` `"workspaces"`) |
| 222 | +4. Install it as a dependency of the consuming app |
| 223 | + |
| 224 | +These in-repo addons will typically be "unbuilt" -- they point `exports` at source files as described in the Unpublished Addons section above. This avoids the need for a separate build step while still giving you proper package boundaries and clean imports. The consuming app's Vite/Embroider build handles all transpilation. |
| 225 | + |
| 226 | +#### Publishing |
| 227 | + |
| 228 | +1. Write code in `src/`, tests with `#src/*` imports |
| 229 | +2. `npm run build` runs Rollup with publish configs, producing `dist/` and `declarations/` |
| 230 | +3. `npm publish` ships only `files` from package.json |
| 231 | +4. Consumers import via `exports`, not internal paths |
| 232 | + |
| 233 | +### Resources |
| 234 | + |
| 235 | +- [@ember/addon-blueprint README](https://github.com/ember-cli/ember-addon-blueprint#readme) |
| 236 | +- [Addon Author Guide](https://github.com/embroider-build/embroider/blob/main/docs/addon-author-guide.md) |
| 237 | +- [Porting Addons to V2](https://github.com/embroider-build/embroider/blob/main/docs/porting-addons-to-v2.md) |
| 238 | +- [Node.js Package Exports](https://nodejs.org/api/packages.html#exports) |
| 239 | +- [Glint Documentation](https://typed-ember.gitbook.io/glint/) |
| 240 | + |
| 241 | +## Drawbacks |
| 242 | + |
| 243 | +- Some advanced use cases (monorepos, custom builds) need additional configuration. |
| 244 | +- Addon authors unfamiliar with TypeScript/Glint face a learning curve, but JavaScript is fully supported. |
| 245 | +- The blueprint is opinionated, but covers the vast majority of use cases. |
| 246 | +- hbs is not supported in addons (it's not possible to use Glint 2 with hbs) |
| 247 | + |
| 248 | +## Alternatives |
| 249 | + |
| 250 | +- Do nothing -- this should have shipped years ago. The community has already broadly adopted v2 addons as the de facto default; the official defaults are lagging behind actual community practice. |
| 251 | +- Default to monorepo (too complex for most users (and maintainers of the bluleprints, as it turns out)) |
| 252 | +- Provide multiple blueprints (maintenance burden, confusion) |
| 253 | + - this is slightly addressed by documenting how to compose blueprints for differentt workflows, like having multiple test apps, for example. |
| 254 | +- we can make hbs work without ts support |
| 255 | + - or we can document how to hand-roll types for these |
| 256 | + |
| 257 | + |
| 258 | +## Appendix, related information, goals, direction |
| 259 | + |
| 260 | +### Influence on Future App Blueprint |
| 261 | + |
| 262 | +The test setup here could be a proof-of-concept for future compat-less Ember apps: |
| 263 | + |
| 264 | +- A minimal Ember app running on Vite with no webpack or `ember-cli-build.js` |
| 265 | +- Bootstrap with just `EmberApp` and `EmberRouter` -- no complex build pipeline |
| 266 | +- ES modules and `import.meta.glob` for module discovery instead of AMD/requirejs |
| 267 | +- Direct framework API usage instead of ember-cli abstractions |
| 268 | + |
| 269 | +This validates that Ember apps can run well on modern build tools, pointing toward simpler app blueprints in the future. |
| 270 | + |
| 271 | +### Non-Built Addons |
| 272 | + |
| 273 | +The blueprint should support a flag (e.g. `--no-build`) that generates a simpler addon intended for local/workspace use rather than publishing to npm. A non-built addon skips the publish-time build entirely -- `exports` points at source files, and the consuming app's build tooling (Vite/Embroider) handles transpilation. |
| 274 | + |
| 275 | +This variant drops a significant chunk of devDependencies and config files that only exist to support the publish build. |
| 276 | + |
| 277 | +The exact flag name and implementation are details of the addon-blueprint itself, not this RFC. The important thing is that the blueprint provides a first-class path for this use case, since local/workspace addons that don't need their own build are common and should not require manually stripping down a full publishable addon scaffold. |
| 278 | + |
0 commit comments