@@ -4,65 +4,75 @@ import path from 'path';
44import type * as Babel from '@babel/core' ;
55import type { types as t } from '@babel/core' ;
66import type { NodePath } from '@babel/traverse' ;
7+ import { ImportUtil } from 'babel-import-util' ;
8+
9+ interface State {
10+ importer : ImportUtil ;
11+ }
712
8- /**
9- * Based on `babel-plugin-ember-modules-api-polyfill`.
10- * @see https://github.com/ember-cli/babel-plugin-ember-modules-api-polyfill/blob/master/src/index.js
11- */
1213export default function ( babel : typeof Babel ) {
1314 const t = babel . types ;
1415
15- const MODULE = '@glimmer/tracking' ;
16+ const REAL_MODULE = '@glimmer/tracking' ;
1617 const IMPORT = 'cached' ;
17- const REPLACED_MODULE = 'ember-cached-decorator-polyfill' ;
18+ const POLYFILL_MODULE = 'ember-cached-decorator-polyfill' ;
19+ const AVAILABE_AT = '>= 4.1.0-alpha.0' ;
20+
21+ // Notice that the only name we introduce into scope here is %%local%%, and we
22+ // know that name is safe to use because it's the name the user was already
23+ // using in the ImportSpecifier that we're replacing.
24+ let loader = babel . template ( `
25+ let %%local%% = %%macroCondition%%(%%dependencySatisfies%%('ember-source', '${ AVAILABE_AT } ')) ?
26+ %%importSync%%('${ REAL_MODULE } ').${ IMPORT } :
27+ %%importSync%%('${ POLYFILL_MODULE } ').${ IMPORT } ;
28+ ` ) ;
1829
1930 return {
2031 name : 'ember-cache-decorator-polyfill' ,
2132 visitor : {
22- ImportDeclaration ( path : NodePath < t . ImportDeclaration > ) {
23- const node = path . node ;
24- const declarations : t . ImportDeclaration [ ] = [ ] ;
25- const removals : NodePath < t . Node > [ ] = [ ] ;
26- const specifiers = path . get ( 'specifiers' ) ;
27- const importPath = node . source . value ;
28-
29- // Only walk specifiers if this is a module we have a mapping for
30- if ( importPath !== MODULE ) {
33+ Program ( path : NodePath < t . Program > , state : State ) {
34+ state . importer = new ImportUtil ( t , path ) ;
35+ } ,
36+ ImportDeclaration ( path : NodePath < t . ImportDeclaration > , state : State ) {
37+ if ( path . node . source . value !== REAL_MODULE ) {
3138 return ;
3239 }
3340
34- // Iterate all the specifiers and attempt to locate their mapping
35- for ( const specifierPath of specifiers ) {
36- const names = getNames ( specifierPath ) ;
37- if ( ! names ) {
38- continue ;
39- }
40- if ( names . imported !== IMPORT ) {
41+ for ( let specifierPath of path . get ( 'specifiers' ) ) {
42+ let names = getNames ( specifierPath ) ;
43+ if ( names ?. imported !== IMPORT ) {
4144 continue ;
4245 }
4346
44- removals . push ( specifierPath ) ;
45-
46- declarations . push (
47- t . importDeclaration (
48- [
49- t . importSpecifier (
50- t . identifier ( names . local ) ,
51- t . identifier ( IMPORT )
52- ) ,
53- ] ,
54- t . stringLiteral ( REPLACED_MODULE )
55- )
47+ // using babel-import-util to gain access to these functions ensures
48+ // that we will never smash any existing bindings (and we'll reuse
49+ // existing imports for these if they exist)
50+ let importSync = state . importer . import (
51+ path ,
52+ '@embroider/macros' ,
53+ 'importSync'
54+ ) ;
55+ let macroCondition = state . importer . import (
56+ path ,
57+ '@embroider/macros' ,
58+ 'macroCondition'
59+ ) ;
60+ let dependencySatisfies = state . importer . import (
61+ path ,
62+ '@embroider/macros' ,
63+ 'dependencySatisfies'
5664 ) ;
57- }
5865
59- if ( removals . length > 0 ) {
60- if ( removals . length === node . specifiers . length ) {
61- path . replaceWithMultiple ( declarations ) ;
62- } else {
63- removals . forEach ( ( specifierPath ) => specifierPath . remove ( ) ) ;
64- path . insertAfter ( declarations ) ;
65- }
66+ specifierPath . remove ( ) ;
67+
68+ path . insertAfter (
69+ loader ( {
70+ local : t . identifier ( names . local ) ,
71+ macroCondition,
72+ dependencySatisfies,
73+ importSync,
74+ } )
75+ ) ;
6676 }
6777 } ,
6878 } ,
@@ -81,7 +91,7 @@ function getNames(
8191 if ( specifierPath . isImportDefaultSpecifier ( ) ) {
8292 return { imported : 'default' , local : specifierPath . node . local . name } ;
8393 } else if ( specifierPath . isImportSpecifier ( ) ) {
84- const importedNode = specifierPath . node . imported ;
94+ let importedNode = specifierPath . node . imported ;
8595 if ( importedNode . type === 'Identifier' ) {
8696 return {
8797 imported : importedNode . name ,
0 commit comments