Skip to content

Commit ab625bd

Browse files
authored
Merge pull request emberjs#1001 from emberjs/deprecate-named-inject
Deprecate named `inject` export from `@ember/service`
2 parents 11dfa15 + a0b8c23 commit ab625bd

File tree

1 file changed

+191
-0
lines changed

1 file changed

+191
-0
lines changed

text/1001-deprecate-named-inject.md

+191
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
---
2+
stage: accepted
3+
start-date: 2023-12-26T00:00:00.000Z
4+
release-date:
5+
release-versions:
6+
teams: # delete teams that aren't relevant
7+
- framework
8+
- typescript
9+
prs:
10+
accepted: https://github.com/emberjs/rfcs/pull/1001
11+
project-link:
12+
---
13+
14+
<!---
15+
Directions for above:
16+
17+
stage: Leave as is
18+
start-date: Fill in with today's date, 2032-12-01T00:00:00.000Z
19+
release-date: Leave as is
20+
release-versions: Leave as is
21+
teams: Include only the [team(s)](README.md#relevant-teams) for which this RFC applies
22+
prs:
23+
accepted: Fill this in with the URL for the Proposal RFC PR
24+
project-link: Leave as is
25+
-->
26+
27+
# Deprecate named `inject` export from `@ember/service`
28+
29+
## Summary
30+
31+
As of [`ember-source@4.1`](https://blog.emberjs.com/ember-4-1-released) (and [RFC#752](https://github.com/emberjs/rfcs/pull/752)), `inject` is an old alias that's no longer needed
32+
33+
## Motivation
34+
35+
`import { service } from '@ember/service'`
36+
makes more sense than
37+
`import { inject as service } from '@ember/service'`
38+
39+
This allows us to slim down our public API surface area to more of _what's needed_.
40+
41+
42+
## Transition Path
43+
44+
Most folks can do a mass find and replace switch from `inject as service` to just `service`.
45+
46+
An example codemod could look [something like this](https://astexplorer.net/#/gist/119f88339ea024e7cde63c71f52ce216/4d128a1239cbb56e00a69d3f710d67c20ed0e431)
47+
```js
48+
export const parser = 'ts'
49+
50+
export default function transformer(file, api) {
51+
const j = api.jscodeshift;
52+
53+
const importNames = new Set();
54+
55+
const root = j(file.source);
56+
57+
// find things we want to get rid of
58+
root
59+
.find(j.ImportSpecifier)
60+
.forEach(path => {
61+
if (path.node.imported.name === 'inject') {
62+
importNames.add(path.node.local.name);
63+
}
64+
})
65+
66+
// now it's time to replace
67+
root.find(j.ClassProperty).forEach(path => {
68+
let node = path.node;
69+
70+
let hasInject = hasDecorators(node, [...importNames.values()]);
71+
72+
if (!hasInject) return;
73+
74+
node.decorators = node.decorators.map(decorator => {
75+
let { expression } = decorator;
76+
77+
if (expression.type === 'Identifier') {
78+
if (importNames.has(expression.name)) {
79+
decorator.expression = j.identifier('service');
80+
}
81+
}
82+
83+
if (expression.type === 'CallExpression') {
84+
decorator.expression.callee = j.identifier('service');
85+
}
86+
87+
return decorator;
88+
});
89+
});
90+
91+
return root.toSource();
92+
}
93+
94+
// Copied from: https://github.com/NullVoxPopuli/ember-concurrency-codemods/tree/main
95+
function firstMatchingDecorator(node, named = []) {
96+
if (!node.decorators) return;
97+
98+
return node.decorators.find((decorator) => {
99+
let { expression } = decorator;
100+
101+
switch (expression.type) {
102+
case 'MethodDefinition': {
103+
}
104+
case 'CallExpression': {
105+
let { callee } = expression;
106+
107+
switch (callee.type) {
108+
case 'Identifier':
109+
return named.includes(callee.name);
110+
case 'MemberExpression': {
111+
let { object } = callee;
112+
113+
return named.includes(object.callee.name);
114+
}
115+
}
116+
}
117+
case 'Identifier':
118+
return named.includes(expression.name);
119+
}
120+
});
121+
}
122+
123+
function hasDecorators(node, named = []) {
124+
return Boolean(firstMatchingDecorator(node, named));
125+
}
126+
```
127+
128+
<details><summary>The test scenarios</summary>
129+
130+
```ts
131+
import { inject } from '@ember/service';
132+
import { inject as service } from '@ember/service';
133+
// import Service from '@ember/service';
134+
import BaseService from '@ember/service';
135+
import { inject as serviceDecorator } from '@ember/service';
136+
import { inject as x } from '@ember/service';
137+
// import { service } from '@ember/service';
138+
import { service as y } from '@ember/service';
139+
// import Service, { inject, service } from '@ember/service';
140+
import Service, { inject as s } from '@ember/service';
141+
142+
143+
export default class Demo extends Service {
144+
145+
}
146+
147+
export default class Demo2 extends BaseService {
148+
// simple
149+
@inject router;
150+
@service router1;
151+
@x router2;
152+
@y router3;
153+
@serviceDecorator router4;
154+
@inject('router') router41;
155+
156+
// TS-only
157+
@inject declare router5: Type;
158+
@inject('router') declare router51: Type;
159+
@service declare router6: Type;
160+
@x declare router7: Type;
161+
@y declare router8: Type;
162+
@serviceDecorator declare router9: Type;
163+
}
164+
```
165+
166+
</detailS>
167+
168+
169+
## How We Teach This
170+
171+
The docs / guides already use the new import path.
172+
173+
## Drawbacks
174+
175+
As with any deprecation, we introduce an upgrade cliff for addons that are updated infrequently, and consequently their consuming apps.
176+
As a mitigation, we could, for v1 addons, add an additional transform to ember-cli-babel to automatically upgrade `inject` from `@ember/service` to `service`.
177+
This does narrow the range a bit, as `service` was introduced in ember-source@4.1, so libraries could not support from 3.28 to 6 (or whichever major ends up removing the `inject`) without adding `@embroider/macros` to conditionally import `inject` or `service` based on the consumer's ember-source version.
178+
179+
## Alternatives
180+
181+
do nothing, the cost of an export alias is:
182+
- a few extra bytes
183+
- mental gymnastics for teaching
184+
- "another case to cover" for tooling
185+
186+
add a lint against `inject`
187+
- all the downsides of the above ("do nothing") may still be present
188+
189+
## Unresolved questions
190+
191+
n/a

0 commit comments

Comments
 (0)