@@ -4,65 +4,75 @@ import path from 'path';
4
4
import type * as Babel from '@babel/core' ;
5
5
import type { types as t } from '@babel/core' ;
6
6
import type { NodePath } from '@babel/traverse' ;
7
+ import { ImportUtil } from 'babel-import-util' ;
8
+
9
+ interface State {
10
+ importer : ImportUtil ;
11
+ }
7
12
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
- */
12
13
export default function ( babel : typeof Babel ) {
13
14
const t = babel . types ;
14
15
15
- const MODULE = '@glimmer/tracking' ;
16
+ const REAL_MODULE = '@glimmer/tracking' ;
16
17
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
+ ` ) ;
18
29
19
30
return {
20
31
name : 'ember-cache-decorator-polyfill' ,
21
32
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 ) {
31
38
return ;
32
39
}
33
40
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 ) {
41
44
continue ;
42
45
}
43
46
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'
56
64
) ;
57
- }
58
65
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
+ ) ;
66
76
}
67
77
} ,
68
78
} ,
@@ -81,7 +91,7 @@ function getNames(
81
91
if ( specifierPath . isImportDefaultSpecifier ( ) ) {
82
92
return { imported : 'default' , local : specifierPath . node . local . name } ;
83
93
} else if ( specifierPath . isImportSpecifier ( ) ) {
84
- const importedNode = specifierPath . node . imported ;
94
+ let importedNode = specifierPath . node . imported ;
85
95
if ( importedNode . type === 'Identifier' ) {
86
96
return {
87
97
imported : importedNode . name ,
0 commit comments