Skip to content

Commit

Permalink
Merge pull request #175 from NullVoxPopuli/add-option-to-preserve-emb…
Browse files Browse the repository at this point in the history
…er-import
  • Loading branch information
rwjblue authored Feb 16, 2021
2 parents afdd503 + 61b457e commit 51a9b4b
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 8 deletions.
46 changes: 46 additions & 0 deletions __tests__/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,52 @@ describe('options', () => {
expect(actual).toEqual(`var _x = Ember.assert;var _y = Ember.inspect;`);
});
});

describe('useEmberModule', () => {
it('does not add Ember import when no Ember related imports are needed', () => {
let input = `console.log('hi mom!');`;
let actual = transform(input, [[Plugin, { useEmberModule: true }]]);

expect(actual).toEqual(input);
});

it(`adds the ember import when used in sub-modules`, () => {
let input = `import Component from '@ember/component';export default class extends Component {}`;
let actual = transform(input, [[Plugin, { useEmberModule: true }]]);
let expected = `import _Ember from 'ember';\nexport default class extends _Ember.Component {}`;

expect(actual).toEqual(expected);
});

it(`keeps the ember import`, () => {
let input = `import Ember from 'ember';let x = Ember;`;
let actual = transform(input, [[Plugin, { useEmberModule: true }]]);

expect(actual).toEqual(input);
});

it(`reuses a pre-existing ember import`, () => {
let input = `import Ember from 'ember'; import Component from '@ember/component'; export default class extends Component {}`;
let actual = transform(input, [[Plugin, { useEmberModule: true }]]);
let expected = `import Ember from 'ember';export default class extends Ember.Component {}`;

expect(actual).toEqual(expected);
});

it(`keeps the ember import when renamed`, () => {
let input = `import BestFramework from 'ember';let x = BestFramework;`;
let actual = transform(input, [[Plugin, { useEmberModule: true }]]);

expect(actual).toEqual(input);
});

it(`import then export`, () => {
let input = `import mbr from 'ember';export const Ember = mbr;`;
let actual = transform(input, [[Plugin, { useEmberModule: true }]]);

expect(actual).toEqual(input);
});
});
});

describe(`import from 'ember'`, () => {
Expand Down
70 changes: 62 additions & 8 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,17 @@ module.exports = function (babel) {
reverseMapping[importRoot][importName] = imported;
});

function getMemberExpressionFor(global) {
function getMemberExpressionFor(global, emberIdentifier) {
let parts = global.split('.');

let object = parts.shift();
let property = parts.shift();

let objectIdentifier =
object === 'Ember' ? emberIdentifier : t.identifier(object);

let memberExpression = t.MemberExpression(
t.identifier(object),
objectIdentifier,
t.identifier(property)
);

Expand All @@ -84,8 +87,49 @@ module.exports = function (babel) {
return {
name: 'ember-modules-api-polyfill',
visitor: {
Program(path, state) {
let options = state.opts || {};
let useEmberModule = Boolean(options.useEmberModule);

let preexistingEmberImportDeclaration = path
.get('body')
.filter((n) => n.type === 'ImportDeclaration')
.find((n) => n.get('source').get('value').node === 'ember');

if (
// an import was found
preexistingEmberImportDeclaration &&
// this accounts for `import from 'ember'` without a local identifier
preexistingEmberImportDeclaration.node.specifiers.length > 0
) {
state.emberIdentifier =
preexistingEmberImportDeclaration.node.specifiers[0].local;
}

state.ensureEmberImport = () => {
if (!useEmberModule) {
// ensures that we can always assume `state.emberIdentifier` is set
state.emberIdentifier = t.identifier('Ember');
return;
}

if (state.emberIdentifier) return;

state.emberIdentifier = path.scope.generateUidIdentifier('Ember');

let emberImport = t.importDeclaration(
[t.importDefaultSpecifier(state.emberIdentifier)],
t.stringLiteral('ember')
);

path.unshiftContainer('body', emberImport);
};
},

ImportDeclaration(path, state) {
let ignore = (state.opts && state.opts.ignore) || [];
let options = state.opts || {};
let ignore = options.ignore || [];
let useEmberModule = Boolean(options.useEmberModule);
let node = path.node;
let declarations = [];
let removals = [];
Expand All @@ -105,10 +149,14 @@ module.exports = function (babel) {

if (specifierPath) {
let local = specifierPath.node.local;
if (local.name !== 'Ember') {
path.scope.rename(local.name, 'Ember');

// when `useEmberModule` is set, we don't need to do anything here
if (!useEmberModule) {
if (local.name !== 'Ember') {
path.scope.rename(local.name, 'Ember');
}
removals.push(specifierPath);
}
removals.push(specifierPath);
} else {
// import 'ember';
path.remove();
Expand Down Expand Up @@ -168,12 +216,15 @@ module.exports = function (babel) {

removals.push(specifierPath);

// ensure that the Ember global is imported if needed
state.ensureEmberImport();

if (
path.scope.bindings[local.name].referencePaths.find(
(rp) => rp.parent.type === 'ExportSpecifier'
)
) {
// not safe to use path.scope.rename directly
// not safe to use path.scope.rename directly when this identifier is being directly re-exported
declarations.push(
t.variableDeclaration('var', [
t.variableDeclarator(
Expand Down Expand Up @@ -215,7 +266,10 @@ module.exports = function (babel) {
// Replace the occurrences of the imported name with the global name.
referencePaths.forEach((referencePath) => {
if (!isTypescriptNode(referencePath.parentPath)) {
const memberExpression = getMemberExpressionFor(global);
const memberExpression = getMemberExpressionFor(
global,
state.emberIdentifier
);

try {
referencePath.replaceWith(memberExpression);
Expand Down

0 comments on commit 51a9b4b

Please sign in to comment.