Skip to content

Commit 7ba66b1

Browse files
authored
Merge branch 'main' into nvp/remove-build-hooks
2 parents 0acc4b1 + 40ffee8 commit 7ba66b1

1 file changed

Lines changed: 278 additions & 0 deletions

File tree

text/0985-v2-addon-by-default.md

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
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

Comments
 (0)