Skip to content

Commit 5cfa048

Browse files
authored
Merge pull request #184 from ember-polyfills/dependency-satisfies-check
Macro-based ember version check
2 parents 387f0bd + 5532da8 commit 5cfa048

File tree

7 files changed

+1977
-3960
lines changed

7 files changed

+1977
-3960
lines changed

.eslintrc.js

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ module.exports = {
2020
rules: {
2121
'@typescript-eslint/no-explicit-any': 'off',
2222
'@typescript-eslint/no-empty-function': 'off',
23+
'prefer-const': 'off',
2324
},
2425

2526
overrides: [

README.md

+20-1
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,15 @@ ember install ember-cached-decorator-polyfill
2121

2222
For addons, pass the `-S` flag.
2323

24+
If you're working in an environment with an explicit Babel config (like a V2
25+
addon or an app with `ember-cli-babel`'s `{ useBabelConfig: true }`
26+
mode), see "Explicit Babel Config" below.
27+
2428
## Compatibility
2529

2630
- Ember.js v3.13 or above
2731
- Ember CLI v2.13 or above
28-
- Node.js v10 or above
32+
- Node.js v14 or above
2933

3034
## Summary
3135

@@ -65,3 +69,18 @@ import 'ember-cached-decorator-polyfill';
6569

6670
Once the upstream types have been updated to reflect RFC 566, this will no
6771
longer be necessary.
72+
73+
## Explicit Babel Config
74+
75+
In environments where you have an explicit Babel config (like authoring a V2
76+
addon) you will need to configure this polyfill's babel plugin. Add it to your
77+
`babel.config.js` like:
78+
79+
```
80+
{
81+
"plugins": [
82+
"ember-cached-decorator-polyfill/babel-plugin"
83+
]
84+
}
85+
```
86+

index.js

+9
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,16 @@ module.exports = {
99
included(parent) {
1010
this._super.included.apply(this, arguments);
1111

12+
// this adds our babel plugin to our parent package
1213
this.addBabelPlugin(parent);
14+
15+
// this ensures our parent package can process macros, since our babel
16+
// plugin emits macros
17+
if (!this.parent.addons.find((a) => a.name === '@embroider/macros')) {
18+
this.addons
19+
.find((a) => a.name === '@embroider/macros')
20+
.installBabelPlugin(parent);
21+
}
1322
},
1423

1524
addBabelPlugin(parent) {

lib/transpile-modules.ts

+53-43
Original file line numberDiff line numberDiff line change
@@ -4,65 +4,75 @@ import path from 'path';
44
import type * as Babel from '@babel/core';
55
import type { types as t } from '@babel/core';
66
import 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-
*/
1213
export 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,

package.json

+10-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
"memoized",
1515
"memoization"
1616
],
17+
"exports": {
18+
".": "./index.js",
19+
"./babel-plugin": "./lib/transpile-modules.js"
20+
},
1721
"homepage": "https://github.com/ember-polyfills/ember-cached-decorator-polyfill#readme",
1822
"bugs": {
1923
"url": "https://github.com/ember-polyfills/ember-cached-decorator-polyfill/issues"
@@ -44,7 +48,9 @@
4448
"test:ember-compatibility": "ember try:each"
4549
},
4650
"dependencies": {
51+
"@embroider/macros": "^1.8.3",
4752
"@glimmer/tracking": "^1.1.2",
53+
"babel-import-util": "^1.2.2",
4854
"ember-cache-primitive-polyfill": "^1.0.1",
4955
"ember-cli-babel": "^7.26.11",
5056
"ember-cli-babel-plugin-helpers": "^1.1.1"
@@ -56,7 +62,7 @@
5662
"@glimmer/component": "^1.1.2",
5763
"@types/babel__core": "^7.1.19",
5864
"@types/babel__traverse": "^7.18.1",
59-
"@types/ember": "^3.16.0",
65+
"@types/ember": "^4.0.1",
6066
"@types/ember-qunit": "^5.0.1",
6167
"@types/ember-resolver": "^5.0.9",
6268
"@types/ember__test-helpers": "^2.8.1",
@@ -112,6 +118,9 @@
112118
"before": "ember-cli-babel",
113119
"configPath": "tests/dummy/config"
114120
},
121+
"peerDependencies": {
122+
"ember-source": "^3.13.0 | ^4.0.0"
123+
},
115124
"release-it": {
116125
"plugins": {
117126
"release-it-lerna-changelog": {

tests/dummy/app/router.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import EmberRouter from '@ember/routing/router';
22
import config from 'dummy/config/environment';
33

44
export default class Router extends EmberRouter {
5-
location = config.locationType;
5+
location = config.locationType as 'auto';
66
rootURL = config.rootURL;
77
}
88

0 commit comments

Comments
 (0)