Skip to content

Commit

Permalink
Merge pull request #274 from pzuraq/feat/decorator-and-class-field-su…
Browse files Browse the repository at this point in the history
…pport

 [FEAT] Decorator and Class Field Support
  • Loading branch information
rwjblue authored Mar 27, 2019
2 parents 7be5512 + 80e42a4 commit 591de2b
Show file tree
Hide file tree
Showing 10 changed files with 797 additions and 554 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module.exports = {
root: true,
parser: 'babel-eslint',
parserOptions: {
ecmaVersion: 2017,
sourceType: 'module'
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ interface EmberCLIBabelConfig {
disableDebugTooling?: boolean;
disablePresetEnv?: boolean;
disableEmberModulesAPIPolyfill?: boolean;
disableDecoratorTransforms?: boolean;
extensions?: string[];
};
}
Expand Down
33 changes: 33 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ module.exports = {
let addonProvidedConfig = this._getAddonProvidedConfig(config);
let shouldCompileModules = this._shouldCompileModules(config);
let shouldIncludeHelpers = this._shouldIncludeHelpers(config);
let shouldIncludeDecoratorPlugins = this._shouldIncludeDecoratorPlugins(config);

let emberCLIBabelConfig = config['ember-cli-babel'];
let shouldRunPresetEnv = true;
Expand Down Expand Up @@ -294,6 +295,7 @@ module.exports = {
options.plugins = [].concat(
shouldIncludeHelpers && this._getHelpersPlugin(),
userPlugins,
shouldIncludeDecoratorPlugins && this._getDecoratorPlugins(config),
this._getDebugMacroPlugins(config),
this._getEmberModulesAPIPolyfill(config),
shouldCompileModules && this._getModulesPlugin(),
Expand All @@ -315,6 +317,37 @@ module.exports = {
return options;
},

_shouldIncludeDecoratorPlugins(config) {
let customOptions = config['ember-cli-babel'] || {};

return customOptions.disableDecoratorTransforms !== true;
},

_getDecoratorPlugins(config) {
const { hasPlugin } = require('ember-cli-babel-plugin-helpers');

// hasPlugin expects to receive a target with an options hash, which is the
// config. We should make it more generic upstream.
let target = { options: config };
let plugins = [];

if (
hasPlugin(target, '@babel/plugin-proposal-decorators')
|| hasPlugin(target, '@babel/plugin-proposal-class-properties')
) {
if (this.parent === this.project) {
this.project.ui.writeWarnLine(`${
this._parentName()
} has added the decorators and/or class properties plugins to its build, but ember-cli-babel provides these by default now! You can remove the transforms, or the addon that provided them, such as @ember-decorators/babel-transforms. Ember supports the stage 1 decorator spec and transforms, so if you were using stage 2, you'll need to ensure that your decorators are compatible, or convert them to stage 1.`);
}
} else {
plugins.push([require.resolve('@babel/plugin-proposal-decorators'), { legacy: true }]);
plugins.push([require.resolve('@babel/plugin-proposal-class-properties'), { loose: true }]);
}

return plugins;
},

_getDebugMacroPlugins(config) {
let addonOptions = config['ember-cli-babel'] || {};

Expand Down
41 changes: 40 additions & 1 deletion node-tests/addon-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,8 @@ describe('ember-cli-babel', function() {
compileModules: false,
disablePresetEnv: true,
disableDebugTooling: true,
disableEmberModulesAPIPolyfill: true
disableEmberModulesAPIPolyfill: true,
disableDecoratorTransforms: true,
}
});

Expand Down Expand Up @@ -683,6 +684,44 @@ describe('ember-cli-babel', function() {
});
});

describe('_getDecoratorPlugins', function() {
it('should include babel transforms by default', function() {
expect(this.addon._getDecoratorPlugins({}).length).to.equal(2, 'plugins added correctly');
});

it('should not include babel transforms if it detects decorators plugin', function() {
this.addon.project.ui = {
writeWarnLine(message) {
expect(message).to.match(/has added the decorators and\/or class properties plugins to its build/);
}
};

expect(this.addon._getDecoratorPlugins({
babel: {
plugins: [
['@babel/plugin-proposal-decorators']
]
}
}).length).to.equal(0, 'plugins were not added');
});

it('should not include babel transforms if it detects class fields plugin', function() {
this.addon.project.ui = {
writeWarnLine(message) {
expect(message).to.match(/has added the decorators and\/or class properties plugins to its build/);
}
};

expect(this.addon._getDecoratorPlugins({
babel: {
plugins: [
['@babel/plugin-proposal-class-properties']
]
}
}).length).to.equal(0, 'plugins were not added');
});
});

describe('_shouldIncludeHelpers()', function() {
beforeEach(function() {
this.addon.app = {
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
},
"dependencies": {
"@babel/core": "^7.0.0",
"@babel/plugin-proposal-class-properties": "^7.3.4",
"@babel/plugin-proposal-decorators": "^7.3.0",
"@babel/plugin-transform-modules-amd": "^7.0.0",
"@babel/plugin-transform-runtime": "^7.2.0",
"@babel/polyfill": "^7.0.0",
Expand All @@ -54,11 +56,13 @@
"broccoli-funnel": "^2.0.1",
"broccoli-source": "^1.1.0",
"clone": "^2.1.2",
"ember-cli-babel-plugin-helpers": "^1.0.2",
"ember-cli-version-checker": "^2.1.2",
"ensure-posix-path": "^1.0.2",
"semver": "^5.5.0"
},
"devDependencies": {
"babel-eslint": "^10.0.1",
"broccoli-test-helper": "^1.4.0",
"chai": "^4.1.2",
"co": "^4.6.0",
Expand Down
4 changes: 3 additions & 1 deletion tests/acceptance/simple-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ module('Acceptance | ES6 features work correctly', function(hooks) {
assert.equal('Test Value', this.element.querySelector('#test-input').value, 'Has arrow functions and template string as ES6 feature');
assert.equal('one', this.element.querySelector('#first-value').textContent, 'Has generators as ES6 feature');
assert.equal('two', this.element.querySelector('#last-value').textContent, 'Has generators as ES6 feature');
assert.equal('dog', this.element.querySelector('#animal-value').textContent, 'Has class and getters/setters as ES6 feature');
assert.equal('dog', this.element.querySelector('#animal-value').textContent, 'Has class and getters/setters and decorators as ES6 feature');
assert.equal('mammal', this.element.querySelector('#animal-type').textContent, 'Has class decorators as ES6 feature');
assert.equal('mammal', this.element.querySelector('#static-animal-type').textContent, 'static and fields as an ES6 class feature');
});
});
28 changes: 22 additions & 6 deletions tests/dummy/app/controllers/application.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
import Controller from '@ember/controller';
import { computed } from "@ember/object";
import { A } from "@ember/array";
import { computed } from '@ember/object';
import { A } from '@ember/array';
import Animal from 'dummy/utils/class-animal';

export default Controller.extend({
init() {
this._super(...arguments);
this.animal = new Animal('dog');
},

animalName: computed({
get() {
const animal = new Animal('dog');
return animal.name;
}
return this.animal.name;
},
}),

staticAnimalType: computed({
get() {
return Animal.type;
},
}),

// Just a very roundabout way of using some ES6 features
value: ((test = 'Test') => `${test} ${'Value'}`)(),

// Test a generator (needs the regenerator runtime) and some ES6 constructs (requires the corejs polyfill)
values: A(Array.from({ *[Symbol.iterator]() { yield 'one'; yield 'two'; } }))
values: A(
Array.from({
*[Symbol.iterator]() {
yield 'one';
yield 'two';
},
})
),
});
5 changes: 3 additions & 2 deletions tests/dummy/app/templates/application.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<div id="first-value">{{values.firstObject}}</div>
<div id="last-value">{{values.lastObject}}</div>
<div id="animal-value">{{animalName}}</div>
<div id="animal-type">{{animal.type}}</div>
<div id="static-animal-type">{{staticAnimalType}}</div>


{{outlet}}
{{outlet}}
24 changes: 18 additions & 6 deletions tests/dummy/app/utils/class-animal.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
function nameMacro(prototye, key, desc) {
desc.get = function() {
return this._name;
};

return desc;
}

function addType(target) {
target.prototype.type = target.type;
}

@addType
class Animal {
constructor(name) {
this._name = name
}
static type = 'mammal';

get name() {
return this._name
constructor(name) {
this._name = name;
}

@nameMacro
set name(name) {
this._name = name
this._name = name;
}
}

Expand Down
Loading

0 comments on commit 591de2b

Please sign in to comment.