Skip to content

Latest commit

 

History

History
194 lines (146 loc) · 5.26 KB

1001-deprecate-named-inject.md

File metadata and controls

194 lines (146 loc) · 5.26 KB
stage start-date release-date release-versions teams prs project-link
released
2023-12-26 00:00:00 UTC
ember-source
6.3.0
framework
typescript
accepted ready-for-release released

Deprecate named inject export from @ember/service

Summary

As of [email protected] (and RFC#752), inject is an old alias that's no longer needed

Motivation

import { service } from '@ember/service' makes more sense than import { inject as service } from '@ember/service'

This allows us to slim down our public API surface area to more of what's needed.

Transition Path

Most folks can do a mass find and replace switch from inject as service to just service.

An example codemod could look something like this

export const parser = 'ts'

export default function transformer(file, api) {
  const j = api.jscodeshift;
  
  const importNames = new Set();

  const root = j(file.source);
  
  // find things we want to get rid of
  root
    .find(j.ImportSpecifier)
    .forEach(path => {
      if (path.node.imported.name === 'inject') {
      	importNames.add(path.node.local.name);
      }
    })
  
  // now it's time to replace
  root.find(j.ClassProperty).forEach(path => {
    let node = path.node;
    
    let hasInject = hasDecorators(node, [...importNames.values()]);
    
    if (!hasInject) return;
    
    node.decorators = node.decorators.map(decorator => {
      let { expression } = decorator;
      
      if (expression.type === 'Identifier') {
        if (importNames.has(expression.name)) {
          decorator.expression = j.identifier('service');
        }
      }
      
      if (expression.type === 'CallExpression') {
        decorator.expression.callee = j.identifier('service');
      }
      
      return decorator;
    });
  });
  
  return root.toSource();
}

// Copied from: https://github.com/NullVoxPopuli/ember-concurrency-codemods/tree/main
function firstMatchingDecorator(node, named = []) {
  if (!node.decorators) return;

  return node.decorators.find((decorator) => {
    let { expression } = decorator;

    switch (expression.type) {
      case 'MethodDefinition': {
      }
      case 'CallExpression': {
        let { callee } = expression;

        switch (callee.type) {
          case 'Identifier':
            return named.includes(callee.name);
          case 'MemberExpression': {
            let { object } = callee;

            return named.includes(object.callee.name);
          }
        }
      }
      case 'Identifier':
        return named.includes(expression.name);
    }
  });
}

function hasDecorators(node, named = []) {
  return Boolean(firstMatchingDecorator(node, named));
}
The test scenarios
import { inject } from '@ember/service';
import { inject as service } from '@ember/service';
// import Service from '@ember/service';
import BaseService from '@ember/service';
import { inject as serviceDecorator } from '@ember/service';
import { inject as x } from '@ember/service';
// import { service } from '@ember/service';
import { service as y } from '@ember/service';
// import Service, { inject, service } from '@ember/service';
import Service, { inject as s } from '@ember/service';


export default class Demo extends Service {
  
}

export default class Demo2 extends BaseService {
  // simple
  @inject router;
  @service router1;
  @x router2;
  @y router3;
  @serviceDecorator router4;
  @inject('router') router41;
  
  // TS-only
  @inject declare router5: Type;
  @inject('router') declare router51: Type;
  @service declare router6: Type;
  @x declare router7: Type;
  @y declare router8: Type;
  @serviceDecorator declare router9: Type;
}

How We Teach This

The docs / guides already use the new import path.

Drawbacks

As with any deprecation, we introduce an upgrade cliff for addons that are updated infrequently, and consequently their consuming apps. 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. This does narrow the range a bit, as service was introduced in [email protected], 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.

Alternatives

do nothing, the cost of an export alias is:

  • a few extra bytes
  • mental gymnastics for teaching
  • "another case to cover" for tooling

add a lint against inject

  • all the downsides of the above ("do nothing") may still be present

Unresolved questions

n/a