Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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