Skip to content

Commit

Permalink
[FEAT] Initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Garrett committed May 10, 2020
1 parent 68757d6 commit 2e06a87
Show file tree
Hide file tree
Showing 11 changed files with 279 additions and 45 deletions.
4 changes: 1 addition & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,11 @@ jobs:

# we recommend new addons test the current and previous LTS
# as well as latest stable release (bonus points to beta/canary)
- env: EMBER_TRY_SCENARIO=ember-lts-3.12
- env: EMBER_TRY_SCENARIO=ember-lts-3.13
- env: EMBER_TRY_SCENARIO=ember-lts-3.16
- env: EMBER_TRY_SCENARIO=ember-release
- env: EMBER_TRY_SCENARIO=ember-beta
- env: EMBER_TRY_SCENARIO=ember-canary
- env: EMBER_TRY_SCENARIO=ember-default-with-jquery
- env: EMBER_TRY_SCENARIO=ember-classic

before_install:
- curl -o- -L https://yarnpkg.com/install.sh | bash
Expand Down
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
ember-cache-primitive-polyfill
==============================================================================

[Short description of the addon.]

Polyfills Ember's [cache primitive API](https://github.com/emberjs/rfcs/blob/master/text/0615-autotracking-memoization.md).

```js
import {
createCache,
getValue,
isConst
} from '@glimmer/tracking/primitives/cache';
```

Compatibility
------------------------------------------------------------------------------

* Ember.js v3.12 or above
* Ember.js v3.13 or above
* Ember CLI v2.13 or above
* Node.js v10 or above

Expand All @@ -20,12 +27,6 @@ ember install ember-cache-primitive-polyfill
```


Usage
------------------------------------------------------------------------------

[Longer description of how to use the addon in apps.]


Contributing
------------------------------------------------------------------------------

Expand Down
Empty file removed addon/.gitkeep
Empty file.
Empty file removed app/.gitkeep
Empty file.
32 changes: 2 additions & 30 deletions config/ember-try.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ module.exports = async function() {
useYarn: true,
scenarios: [
{
name: 'ember-lts-3.12',
name: 'ember-lts-3.13',
npm: {
devDependencies: {
'ember-source': '~3.12.0'
'ember-source': '~3.13.0'
}
}
},
Expand Down Expand Up @@ -56,34 +56,6 @@ module.exports = async function() {
devDependencies: {}
}
},
{
name: 'ember-default-with-jquery',
env: {
EMBER_OPTIONAL_FEATURES: JSON.stringify({
'jquery-integration': true
})
},
npm: {
devDependencies: {
'@ember/jquery': '^0.5.1'
}
}
},
{
name: 'ember-classic',
env: {
EMBER_OPTIONAL_FEATURES: JSON.stringify({
'application-template-wrapper': true,
'default-async-observers': false,
'template-only-glimmer-components': false
})
},
npm: {
ember: {
edition: 'classic'
}
}
}
]
};
};
24 changes: 23 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
'use strict';

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

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

this.import('vendor/ember-cache-primitive-polyfill.js');
},

treeForVendor(rawVendorTree) {
let babelAddon = this.addons.find(
addon => addon.name === 'ember-cli-babel'
);

let transpiledVendorTree = babelAddon.transpileTree(rawVendorTree, {
babel: this.options.babel,

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

return transpiledVendorTree;
},
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
},
"dependencies": {
"ember-cli-babel": "^7.19.0",
"ember-cli-htmlbars": "^4.3.1"
"ember-compatibility-helpers": "^1.2.1"
},
"devDependencies": {
"@ember/optional-features": "^1.3.0",
Expand All @@ -35,6 +35,7 @@
"ember-auto-import": "^1.5.3",
"ember-cli": "~3.18.0",
"ember-cli-dependency-checker": "^3.2.0",
"ember-cli-htmlbars": "^4.3.1",
"ember-cli-inject-live-reload": "^2.0.2",
"ember-cli-sri": "^2.1.1",
"ember-cli-uglify": "^3.0.0",
Expand Down
133 changes: 133 additions & 0 deletions tests/unit/cache-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { module, test } from 'qunit';

import { DEBUG } from '@glimmer/env';
import { tracked } from '@glimmer/tracking';
import { createCache, getValue, isConst } from '@glimmer/tracking/primitives/cache';

class Tag {
@tracked __tag__;
}

function createTag() {
return new Tag();
}

function consumeTag(tag) {
tag.__tag__;
}

function dirtyTag(tag) {
tag.__tag__ = undefined;
}

module('tracking cache', () => {
test('it memoizes based on tags that are consumed within a track frame', assert => {
let tag1 = createTag();
let tag2 = createTag();
let count = 0;

let cache = createCache(() => {
consumeTag(tag1);
consumeTag(tag2);

return ++count;
});

assert.equal(getValue(cache), 1, 'called correctly the first time');
assert.equal(getValue(cache), 1, 'memoized result returned second time');

dirtyTag(tag1);
assert.equal(getValue(cache), 2, 'cache busted when tag1 dirtied');
assert.equal(getValue(cache), 2, 'memoized result returned when nothing dirtied');

dirtyTag(tag2);
assert.equal(getValue(cache), 3, 'cache busted when tag2 dirtied');
assert.equal(getValue(cache), 3, 'memoized result returned when nothing dirtied');
});

test('nested memoizations work, and automatically propogate', assert => {
let innerTag = createTag();
let outerTag = createTag();

let innerCount = 0;
let outerCount = 0;

let innerCache = createCache(() => {
consumeTag(innerTag);

return ++innerCount;
});

let outerCache = createCache(() => {
consumeTag(outerTag);

return [++outerCount, getValue(innerCache)];
});

assert.deepEqual(
getValue(outerCache),
[1, 1],
'both functions called correctly the first time'
);
assert.deepEqual(getValue(outerCache), [1, 1], 'memoized result returned correctly');

dirtyTag(outerTag);

assert.deepEqual(
getValue(outerCache),
[2, 1],
'outer result updated, inner result still memoized'
);
assert.deepEqual(getValue(outerCache), [2, 1], 'memoized result returned correctly');

dirtyTag(innerTag);

assert.deepEqual(getValue(outerCache), [3, 2], 'both inner and outer result updated');
assert.deepEqual(getValue(outerCache), [3, 2], 'memoized result returned correctly');
});

test('isConst allows users to check if a memoized function is constant', assert => {
let tag = createTag();

let constCache = createCache(() => {
// do nothing;
});

let nonConstCache = createCache(() => {
consumeTag(tag);
});

getValue(constCache);
getValue(nonConstCache);

assert.ok(isConst(constCache), 'constant cache returns true');
assert.notOk(isConst(nonConstCache), 'non-constant cache returns false');
});

if (DEBUG) {
test('isConst throws an error in DEBUG mode if users attempt to check a function before it has been called', assert => {
let cache = createCache(() => {
// do nothing;
});

assert.throws(
() => isConst(cache),
/isConst\(\) can only be used on a cache once getValue\(\) has been called at least once/
);
});

test('isConst throws an error in DEBUG mode if users attempt to use with a non-cache', assert => {
assert.throws(
() => isConst(123),
/isConst\(\) can only be used on an instance of a cache created with createCache\(\). Called with: 123/
);
});

test('getValue throws an error in DEBUG mode if users to use with a non-cache', assert => {
assert.throws(
() => getValue(123),
/getValue\(\) can only be used on an instance of a cache created with createCache\(\). Called with: 123/
);
});
}
});
Empty file removed vendor/.gitkeep
Empty file.
107 changes: 107 additions & 0 deletions vendor/ember-cache-primitive-polyfill.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import Ember from 'ember';
import { DEBUG } from '@glimmer/env';
import { assert } from '@ember/debug';
import { gte } from 'ember-compatibility-helpers';

(function() {
let track, valueForTag, validateTag, consumeTag, isConstTag;

if (gte('3.17.0-beta.1')) {
let validator = Ember.__loader.require('@glimmer/validator');

track = validator.track;
valueForTag = validator.valueForTag || validator.value;
validateTag = validator.validateTag || validator.validate;
consumeTag = validator.consumeTag || validator.consume;
isConstTag = validator.isConstTag;
} else if (gte('3.13.0-beta.1')) {
let metal = Ember.__loader.require('@ember/-internals/metal');
let reference = Ember.__loader.require('@glimmer/reference');

track = metal.track;
valueForTag = reference.value;
validateTag = reference.validate;
consumeTag = reference.consume;
isConstTag = reference.isConstTag;
} else if (DEBUG) {
throw new Error('Attempted to use cache polyfill with unsupported Ember version');
}

let DEBUG_CACHE;

if (DEBUG) {
DEBUG_CACHE = Symbol('DEBUG_CACHE');
}

class Cache {
lastValue;
tag;
snapshot = -1;

constructor(fn) {
this.fn = fn;

if (DEBUG) {
return { [DEBUG_CACHE]: this };
}
}
}

Ember._createCache = function createCache(fn) {
return new Cache(fn);
}

Ember._cacheGetValue = function getValue(cache) {
if (DEBUG) {
assert(
`getValue() can only be used on an instance of a cache created with createCache(). Called with: ${String(
cache
)}`,
cache[DEBUG_CACHE] instanceof Cache
);

cache = cache[DEBUG_CACHE];
}

let { tag, snapshot, fn } = cache;

if (tag === undefined || !validateTag(tag, snapshot)) {
tag = track(() => (cache.lastValue = fn()));
cache.tag = tag;
cache.snapshot = valueForTag(tag);
consumeTag(tag);
} else {
consumeTag(tag);
}

return cache.lastValue;
}

Ember._cacheIsConst = function isConst(cache) {
if (DEBUG) {
assert(
`isConst() can only be used on an instance of a cache created with createCache(). Called with: ${String(
cache
)}`,
cache[DEBUG_CACHE] instanceof Cache
);

cache = cache[DEBUG_CACHE];

assert(
`isConst() can only be used on a cache once getValue() has been called at least once. Called with cache function:\n\n${String(
cache.fn
)}`,
cache.tag
);
}

return isConstTag(cache.tag);
}
})();

define('@glimmer/tracking/primitives/cache', ['exports'], (exports) => {
exports.createCache = Ember._createCache;
exports.getValue = Ember._cacheGetValue;
exports.isConst = Ember._cacheIsConst;
});
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4497,7 +4497,7 @@ ember-cli@~3.18.0:
watch-detector "^1.0.0"
yam "^1.0.0"

ember-compatibility-helpers@^1.1.2:
ember-compatibility-helpers@^1.1.2, ember-compatibility-helpers@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ember-compatibility-helpers/-/ember-compatibility-helpers-1.2.1.tgz#87c92c4303f990ff455c28ca39fb3ee11441aa16"
integrity sha512-6wzYvnhg1ihQUT5yGqnLtleq3Nv5KNv79WhrEuNU9SwR4uIxCO+KpyC7r3d5VI0EM7/Nmv9Nd0yTkzmTMdVG1A==
Expand Down

0 comments on commit 2e06a87

Please sign in to comment.