diff --git a/__tests__/index-test.js b/__tests__/index-test.js index cfa37f6..9abe526 100644 --- a/__tests__/index-test.js +++ b/__tests__/index-test.js @@ -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'`, () => { diff --git a/src/index.js b/src/index.js index 5eb8703..6e4a452 100644 --- a/src/index.js +++ b/src/index.js @@ -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) ); @@ -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 = []; @@ -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(); @@ -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( @@ -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);