Skip to content

Commit

Permalink
refactor: Use preprocessor for polyfill
Browse files Browse the repository at this point in the history
  • Loading branch information
mydea committed May 12, 2021
1 parent 5759283 commit 1d459ed
Show file tree
Hide file tree
Showing 10 changed files with 227 additions and 284 deletions.
36 changes: 36 additions & 0 deletions addon/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { createCache, getValue } from '@glimmer/tracking/primitives/cache';
import { assert } from '@ember/debug';

export function cached(...args) {
const [target, key, descriptor] = args;

// Error on `@cached()`, `@cached(...args)`, and `@cached propName = value;`
assert(
'You attempted to use @cached(), which is not necessary nor supported. Remove the parentheses and you will be good to go!',
target !== undefined
);
assert(
`You attempted to use @cached on with ${
args.length > 1 ? 'arguments' : 'an argument'
} ( @cached(${args
.map(d => `'${d}'`)
.join(
', '
)}), which is not supported. Dependencies are automatically tracked, so you can just use ${'`@cached`'}`,
typeof target === 'object' &&
typeof key === 'string' &&
typeof descriptor === 'object' &&
args.length === 3
);
assert(
`The @cached decorator must be applied to getters. '${key}' is not a getter.`,
typeof descriptor.get == 'function'
);

const caches = new WeakMap();
const getter = descriptor.get;
descriptor.get = function () {
if (!caches.has(this)) caches.set(this, createCache(getter.bind(this)));
return getValue(caches.get(this));
};
}
57 changes: 5 additions & 52 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,62 +1,15 @@
'use strict';

const { resolve } = require('path');
const ReplaceImportsPreprocessor = require('./lib/replace-imports-preprocessor');

module.exports = {
name: require('./package').name,

included() {
this._super.included.apply(this, arguments);
this._ensureThisImport();

this.import('vendor/ember-cached-decorator-polyfill/index.js');
this.patchEmberModulesAPIPolyfill();
},

treeForVendor(tree) {
const babel = this.addons.find(a => a.name === 'ember-cli-babel');

return babel.transpileTree(tree, {
babel: this.options.babel,

'ember-cli-babel': {
compileModules: false
}
});
},

_ensureThisImport() {
if (!this.import) {
this._findHost = function findHostShim() {
let current = this;
let app;
do {
app = current.app || app;
// eslint-disable-next-line no-cond-assign
} while (current.parent.parent && (current = current.parent));
return app;
};
this.import = function importShim(asset, options) {
const app = this._findHost();
app.import(asset, options);
};
setupPreprocessorRegistry(type, registry) {
if (type !== 'parent') {
return;
}
},

patchEmberModulesAPIPolyfill() {
const babel = this.parent.findOwnAddonByName
? this.parent.findOwnAddonByName('ember-cli-babel') // parent is an addon
: this.parent.findAddonByName('ember-cli-babel'); // parent is an app

if (babel.__CachedDecoratorPolyfillApplied) return;
babel.__CachedDecoratorPolyfillApplied = true;

const { _getEmberModulesAPIPolyfill } = babel;
babel._getEmberModulesAPIPolyfill = function (...args) {
const plugins = _getEmberModulesAPIPolyfill.apply(this, args);
if (!plugins) return;

return [[resolve(__dirname, './lib/transpile-modules.js')], ...plugins];
};
registry.add('js', new ReplaceImportsPreprocessor());
}
};
22 changes: 22 additions & 0 deletions lib/replace-imports-preprocessor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const BroccoliReplace = require('broccoli-replace');

module.exports = class ReplaceImportsPreprocessor {
name = 'ember-cached-decorator-polyfill-preprocessor';

toTree(tree) {
return new BroccoliReplace(tree, {
files: ['**/*.js', '**/*.ts'],
patterns: [
{
match: /import\s+{[\s(tracked,)]*cached[\s(,tracked)]*}\s*from\s*['"]@glimmer\/tracking['"]/m,
replacement: str => {
return str.includes('tracked')
? `import { tracked } from '@glimmer/tracking';
import { cached } from 'ember-cached-decorator-polyfill';`
: `import { cached } from 'ember-cached-decorator-polyfill';`;
}
}
]
});
}
};
182 changes: 0 additions & 182 deletions lib/transpile-modules.js

This file was deleted.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,14 @@
"test:ember-compatibility": "ember try:each"
},
"dependencies": {
"@glimmer/tracking": "^1.0.4",
"broccoli-replace": "^2.0.2",
"ember-cache-primitive-polyfill": "^1.0.1",
"ember-cli-babel": "^7.21.0"
},
"devDependencies": {
"@ember/optional-features": "^2.0.0",
"@glimmer/component": "^1.0.1",
"@glimmer/tracking": "^1.0.0",
"@types/ember": "^3.16.0",
"@types/ember-qunit": "^3.4.9",
"@types/ember-resolver": "^5.0.9",
Expand Down
33 changes: 33 additions & 0 deletions tests/unit/followed-import-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { module, test } from 'qunit';
import { cached, tracked } from '@glimmer/tracking';

module('Unit | Import | followed import', function () {
test('it works', function (assert) {
class Person {
@tracked firstName = 'Jen';
lastName = 'Weber';

@cached
get fullName() {
const fullName = `${this.firstName} ${this.lastName}`;
assert.step(fullName);
return fullName;
}
}

const person = new Person();
assert.verifySteps([], 'getter is not called after class initialization');

assert.strictEqual(person.fullName, 'Jen Weber');
assert.verifySteps(
['Jen Weber'],
'getter was called after property access'
);

assert.strictEqual(person.fullName, 'Jen Weber');
assert.verifySteps(
[],
'getter was not called again after repeated property access'
);
});
});
37 changes: 37 additions & 0 deletions tests/unit/multi-line-import-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { module, test } from 'qunit';
// prettier-ignore
import {
cached,
tracked
} from "@glimmer/tracking";

module('Unit | Import | multi-line import', function () {
test('it works', function (assert) {
class Person {
@tracked firstName = 'Jen';
lastName = 'Weber';

@cached
get fullName() {
const fullName = `${this.firstName} ${this.lastName}`;
assert.step(fullName);
return fullName;
}
}

const person = new Person();
assert.verifySteps([], 'getter is not called after class initialization');

assert.strictEqual(person.fullName, 'Jen Weber');
assert.verifySteps(
['Jen Weber'],
'getter was called after property access'
);

assert.strictEqual(person.fullName, 'Jen Weber');
assert.verifySteps(
[],
'getter was not called again after repeated property access'
);
});
});
Loading

0 comments on commit 1d459ed

Please sign in to comment.