Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial implementation #1

Merged
merged 2 commits into from
May 15, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
pzuraq marked this conversation as resolved.
Show resolved Hide resolved
* 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',
pzuraq marked this conversation as resolved.
Show resolved Hide resolved
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;
},
};
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@
},
"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",
"@glimmer/component": "^1.0.0",
"@glimmer/tracking": "^1.0.0",
"babel-eslint": "^10.1.0",
"broccoli-asset-rev": "^3.0.0",
"ember-auto-import": "^1.5.3",
"ember-cli": "~3.18.0",
"ember-cli-dependency-checker": "^3.2.0",
"ember-cli-htmlbars": "^4.3.1",
pzuraq marked this conversation as resolved.
Show resolved Hide resolved
"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.
87 changes: 87 additions & 0 deletions vendor/ember-cache-primitive-polyfill.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
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;
consumeTag = metal.consume;
valueForTag = reference.value;
validateTag = reference.validate;
isConstTag = reference.isConstTag;
} else if (DEBUG) {
throw new Error('Attempted to use cache polyfill with unsupported Ember version');
}

class Cache {
__lastValue;
__tag;
__snapshot = -1;

constructor(fn) {
this.__fn = fn;
}
}

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 instanceof Cache
);
}

let { __tag: tag, __snapshot: snapshot, __fn: 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 instanceof 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);
}
})();
Loading