Skip to content
This repository has been archived by the owner on Jan 12, 2020. It is now read-only.

Commit

Permalink
Merge pull request #22 from buschtoens/refactor/stage-2-decorators
Browse files Browse the repository at this point in the history
refactor(tracked): minimally invasive stage 2 decorators support
  • Loading branch information
rwjblue authored Jan 10, 2019
2 parents f4fe6ec + 048c4fc commit e3e51e8
Show file tree
Hide file tree
Showing 9 changed files with 437 additions and 313 deletions.
6 changes: 5 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,15 @@ jobs:
# runs linting and tests with current locked deps
- stage: 'Tests'
env: EMBER_TRY_SCENARIO=ember-lts-3.4
env: EMBER_TRY_SCENARIO=ember-lts-3.4-legacy-decorators
- env: EMBER_TRY_SCENARIO=ember-release
- env: EMBER_TRY_SCENARIO=ember-release-legacy-decorators
- env: EMBER_TRY_SCENARIO=ember-beta
- env: EMBER_TRY_SCENARIO=ember-beta-legacy-decorators
- env: EMBER_TRY_SCENARIO=ember-canary
- env: EMBER_TRY_SCENARIO=ember-canary-legacy-decorators
- env: EMBER_TRY_SCENARIO=ember-default
- env: EMBER_TRY_SCENARIO=ember-default-with-jquery
- env: EMBER_TRY_SCENARIO=ember-default-legacy-decorators
- name: 'Blueprints'
script: yarn nodetest

Expand Down
80 changes: 34 additions & 46 deletions addon/tracked.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import Ember from 'ember';
import { assert } from '@ember/debug';
import { DEBUG } from '@glimmer/env';
import { notifyPropertyChange } from '@ember/object';
import { addObserver } from '@ember/object/observers';
import { decoratorWithParams } from '@ember-decorators/utils/decorator';

function setupObservers(instance: object, dependentKeys: string[], notifyMethod: (() => void)) {
function setupObservers<O extends object>(instance: O, dependentKeys: (keyof O)[], notifyMethod: (() => void)) {
for (let i = 0; i < dependentKeys.length; i++) {
let dependentKey = dependentKeys[i];
addObserver(instance, dependentKey, instance, notifyMethod);
}
}

function descriptorForTrackedComputedProperty(_target: any, key: string | symbol, desc: PropertyDescriptor, dependencies?: string[]) {
function descriptorForTrackedComputedProperty(key: string | symbol, desc: PropertyDescriptor, dependencies?: string[]) {
// TODO: really should use WeakSet here, but that isn't available on IE11
const OBSERVERS_SETUP = new WeakMap();

Expand All @@ -36,15 +37,15 @@ function descriptorForTrackedComputedProperty(_target: any, key: string | symbol
// will be bound to the instance when invoked
function notify(this: object) {
if (typeof key === 'string') {
Ember.notifyPropertyChange(this, key);
notifyPropertyChange(this, key);
} else if (DEBUG) {
throw new Error(`@tracked - unsupported property type ${String(key)}`);
}
}

desc.get = function() {
if (!OBSERVERS_SETUP.has(this) && Array.isArray(dependencies)) {
setupObservers(this, dependencies, notify);
setupObservers<any>(this, dependencies, notify);
}
OBSERVERS_SETUP.set(this, true);

Expand All @@ -54,7 +55,7 @@ function descriptorForTrackedComputedProperty(_target: any, key: string | symbol
if (setterProvided) {
desc.set = function(value) {
if (typeof key === 'string') {
Ember.notifyPropertyChange(this, key);
notifyPropertyChange(this, key);
setterProvided.call(this, value);
} else if (DEBUG) {
throw new Error(`@tracked - unsupported property type ${String(key)}`);
Expand All @@ -65,11 +66,7 @@ function descriptorForTrackedComputedProperty(_target: any, key: string | symbol
return desc;
}

function installTrackedProperty(_target: object, key: string | symbol, descriptor?: PropertyDescriptor & { initializer: (() => void)}): PropertyDescriptor {
// only happens in babel, never in TS (Sept 2018)
// TODO check for whether initializer is a function
const initializer = descriptor && descriptor.initializer;

function installTrackedProperty(key: string | symbol, descriptor?: PropertyDescriptor, initializer?: () => any): PropertyDescriptor {
let values = new WeakMap();

let get;
Expand All @@ -90,54 +87,45 @@ function installTrackedProperty(_target: object, key: string | symbol, descripto
}

return {
configurable: true,
// TODO: correcting a misspelling caused chrome to error
// writable: true,

configurable: descriptor ? descriptor.configurable : true,
enumerable: descriptor ? descriptor.enumerable : true,
get,
set(value) {
if (typeof key === 'string') {
values.set(this, value);
Ember.notifyPropertyChange(this, key);
notifyPropertyChange(this, key);
} else if (DEBUG) {
throw new Error(`@tracked - unsupported property type ${String(key)}`);
}
}
};
}


function _tracked(target: object, key: string | symbol, descriptor?: PropertyDescriptor, dependencies?: string[]): PropertyDescriptor {
// descriptor is undefined for typescript class fields
if (descriptor === undefined || 'initializer' in descriptor) {
return installTrackedProperty(target, key, descriptor);
function _tracked(
key: string | symbol,
descriptor?: PropertyDescriptor,
initializer?: () => any,
dependencies?: string[]
): PropertyDescriptor {
if (!descriptor || typeof descriptor.get !== 'function' && typeof descriptor.set !== 'function') {
return installTrackedProperty(key, descriptor, initializer);
} else {
return descriptorForTrackedComputedProperty(target, key, descriptor, dependencies);
return descriptorForTrackedComputedProperty(key, descriptor, dependencies);
}
}

// type CompatiblePropertyDecorator = (target: object, key: string | symbol, descriptor: PropertyDescriptor) => PropertyDescriptor;

// @tracked
// TODO: replace return w/ PropertyDescriptor once TS gets their decorator act together
export function tracked(target: object, propertyKey: string | symbol, descriptor?: PropertyDescriptor): any;
// @tracked('foo', 'bar')
// TODO: replace return w/ CompatiblePropertyDecorator
export function tracked(...args: string[]): any;
// TODO: replace return w/ CompatiblePropertyDecorator | PropertyDescriptor
export function tracked(
targetOrArgs: (string | object),
secondArg: string | symbol,
descriptorOrString: string | PropertyDescriptor | undefined,
...rest: string[]): any
{
// if called for `@tracked('foo')`
if (typeof targetOrArgs === 'string') { // @tracked('foo', 'bar')
const args = [targetOrArgs, secondArg as string, descriptorOrString as string, ...rest];
return function(target: object, key: string | symbol, descriptor: PropertyDescriptor) {
return _tracked(target, key, descriptor, args);
}
} else { // @tracked
return _tracked(targetOrArgs, secondArg, descriptorOrString as PropertyDescriptor | undefined);
}
}
type TSDecorator = (target: object, propertyKey: string | symbol, descriptor?: PropertyDescriptor) => void;
type TrackedDecorator = TSDecorator & ((...args: string[]) => TSDecorator);

export const tracked: TrackedDecorator = decoratorWithParams((desc, params = []) => {
assert(`@tracked - Can only be used on class fields.`, desc.kind === 'field' || desc.kind === 'method');
const descriptor = _tracked(desc.key, desc.descriptor, desc.initializer, params);

return {
...desc,
descriptor,
kind: 'method',
initializer: undefined
};
});
38 changes: 22 additions & 16 deletions config/ember-try.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
'use strict';

const getChannelURL = require('ember-source-channel-url');
const flatMap = require('lodash.flatmap');
const merge = require('lodash.merge');

const withDecoratorVariants = scenarios =>
flatMap(scenarios, scenario => [
scenario,
merge({}, scenario, {
name: `${scenario.name}-legacy-decorators`,
npm: {
dependencies: {
'ember-cli-typescript': null
},
devDependencies: {
'@ember-decorators/babel-transforms': '^2.1.2',
'ember-cli-typescript': '^1.5.0'
}
}
})
]);

module.exports = function() {
return Promise.all([
getChannelURL('release'),
getChannelURL('beta'),
getChannelURL('canary')
]).then((urls) => {
]).then(urls => {
return {
useYarn: true,
scenarios: [
scenarios: withDecoratorVariants([
{
name: 'ember-lts-2.12',
npm: {
Expand Down Expand Up @@ -72,21 +91,8 @@ module.exports = function() {
npm: {
devDependencies: {}
}
},
{
name: 'ember-default-with-jquery',
env: {
EMBER_OPTIONAL_FEATURES: JSON.stringify({
'jquery-integration': true
})
},
npm: {
devDependencies: {
'@ember/jquery': '^0.5.1'
}
}
}
]
])
};
});
};
17 changes: 10 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,25 @@
"nodetest": "mocha node-tests --recursive"
},
"dependencies": {
"@ember-decorators/utils": "^4.0.0",
"@glimmer/env": "^0.1.7",
"ember-cli-babel": "^7.1.2",
"ember-cli-babel": "^7.2.0",
"ember-cli-get-component-path-option": "^1.0.0",
"ember-cli-is-package-missing": "^1.0.0",
"ember-cli-normalize-entity-name": "^1.0.0",
"ember-cli-path-utils": "^1.0.0",
"ember-cli-string-utils": "^1.1.0",
"ember-cli-typescript": "^2.0.0-rc.1",
"ember-compatibility-helpers": "^1.1.2"
},
"devDependencies": {
"@ember-decorators/babel-transforms": "^2.1.1",
"@ember-decorators/babel-transforms": "^4.0.0",
"@ember/optional-features": "^0.6.1",
"@types/ember": "^3.0.15",
"@types/ember-qunit": "^3.4.0",
"@types/ember-test-helpers": "^1.0.0",
"@types/ember": "~3.0.26",
"@types/ember-qunit": "~3.4.3",
"@types/ember-test-helpers": "~1.0.4",
"@types/ember-testing-helpers": "^0.0.3",
"@types/ember__test-helpers": "^0.7.1",
"@types/ember__test-helpers": "~0.7.6",
"@types/mocha": "^5.2.5",
"@types/qunit": "^2.5.3",
"@types/rsvp": "^4.0.2",
Expand All @@ -55,7 +57,6 @@
"ember-cli-inject-live-reload": "^1.4.1",
"ember-cli-qunit": "^4.3.2",
"ember-cli-sri": "^2.1.0",
"ember-cli-typescript": "^1.4.2",
"ember-cli-uglify": "^2.1.0",
"ember-disable-prototype-extensions": "^1.1.2",
"ember-export-application-global": "^2.0.0",
Expand All @@ -68,6 +69,8 @@
"eslint-plugin-ember": "^5.0.0",
"eslint-plugin-node": "^6.0.1",
"loader.js": "^4.2.3",
"lodash.flatmap": "^4.5.0",
"lodash.merge": "^4.6.1",
"mocha": "^5.0.0",
"qunit-dom": "^0.7.1",
"typescript": "^2.9.2"
Expand Down
10 changes: 9 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@
"sparkles-component/*": [
"addon/*"
],
"sparkles-component/test-support": [
"addon-test-support"
],
"sparkles-component/test-support/*": [
"addon-test-support/*"
],
"*": [
"types/*"
]
Expand All @@ -44,6 +50,8 @@
"app/**/*",
"addon/**/*",
"tests/**/*",
"types/**/*"
"types/**/*",
"test-support/**/*",
"addon-test-support/**/*"
]
}
14 changes: 14 additions & 0 deletions types/@ember-decorators/utils/decorator.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
type MemberDescriptor = {
key: string;
kind: 'class' | 'method' | 'field' | 'initializer';
elements?: MemberDescriptor[];
key: string;
placement?: 'prototype' | 'static' | 'own';
extras?: MemberDescriptor[];
initializer?: () => any;
descriptor?: PropertyDescriptor;
};

export function decoratorWithParams(
fn: (desc: MemberDescriptor, params?: any[]) => MemberDescriptor
): any;
11 changes: 9 additions & 2 deletions types/@ember/component.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
declare module '@ember/component' {
export function setComponentManager<T>(managerId: string, baseClass: T): T;
export function capabilities(version: string, opts?: {
export function setComponentManager<T>(
managerFactory: (owner: Owner) => {},
baseClass: T
): T;
export function capabilities(
version: string,
opts?: {
destructor?: boolean;
asyncLifecycleCallbacks?: boolean;
}): any;
}
): any;
}
1 change: 1 addition & 0 deletions types/ember-compatibility-helpers.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export function gte(version: string): boolean;
Loading

0 comments on commit e3e51e8

Please sign in to comment.