diff --git a/.eslintrc.js b/.eslintrc.js index 50725ee..689e880 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,5 +1,6 @@ module.exports = { root: true, + parser: 'babel-eslint', parserOptions: { ecmaFeatures: { jsx: true, diff --git a/README.md b/README.md index 994ee50..cfbea49 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,38 @@ export default class TodoItem extends React.Component { } ``` +## Object Rest Spread and Class Properties + +[Object Rest Spread]() and [Class Properties](https://babeljs.io/docs/en/babel-plugin-proposal-class-properties) are included in `create-react-app` and used in many React projects. To enable these features you must add respective transforms to Babel plugins setting in your `ember-cli-build.js`. + +For Babel 6, + +1. Add Babel plugin packages to your project `babel-plugin-transform-object-rest-spread` and `babel-plugin-transform-class-properties` +2. In `ember-cli-build.js` file, configure + ```js + let app = new EmberApp(defaults, { + babel: { + plugins: ['transform-class-properties', 'transform-object-rest-spread'], + }, + }); + ``` + +For Babel 7, + +1. Install the transforms `npm install --save-dev @babel/plugin-proposal-object-rest-spread @babel/plugin-proposal-class-properties` +2. Add these plugins to your `ember-cli-build.js` file + +```js +let app = new EmberAddon(defaults, { + babel: { + plugins: [ + '@babel/plugin-proposal-object-rest-spread', + '@babel/plugin-proposal-class-properties', + ], + }, +}); +``` + ## What's Missing There is no React `link-to` equivalent for linking to Ember routes inside of diff --git a/ember-cli-build.js b/ember-cli-build.js index 69cc8bb..0463a23 100644 --- a/ember-cli-build.js +++ b/ember-cli-build.js @@ -8,6 +8,9 @@ module.exports = function(defaults) { 'ember-cli-babel': { includePolyfill: true, }, + babel: { + plugins: ['transform-class-properties', 'transform-object-rest-spread'], + }, }); /* diff --git a/index.js b/index.js index 3f94806..45ea58b 100644 --- a/index.js +++ b/index.js @@ -1,17 +1,49 @@ /* eslint-env node */ 'use strict'; -const react = require('broccoli-react'); +const configureJsxTransform = require('./lib/configure-jsx-transform'); +const mergeTrees = require('broccoli-merge-trees'); +const Funnel = require('broccoli-funnel'); +const path = require('path'); module.exports = { name: 'ember-cli-react', - preprocessTree: function(type, tree) { - if (type === 'js') { - tree = react(tree, { transform: { es6module: true } }); - } + included(parent) { + this._super.included.apply(this, arguments); - return tree; + configureJsxTransform(parent); + + parent.registry.add('js', { + name: 'ember-cli-react', + ext: 'jsx', + toTree(tree) { + // get all JSX files and rename them to js + let jsx = new Funnel(tree, { + include: ['**/*.jsx'], + getDestinationPath(relativePath) { + let f = path.parse(relativePath); + if (f.ext === '.jsx') { + return path.join(f.dir, `${f.name}.js`); + } else { + return relativePath; + } + }, + }); + + // apply preprocessing from other babel plugins to the jsx files + let processed = parent.registry + .load('js') + .filter(p => p.name !== 'ember-cli-react') + .reduce((tree, plugin) => plugin.toTree(tree), jsx); + + let withoutJsx = new Funnel(tree, { + exclude: ['**/*.jsx'], + }); + + return mergeTrees([withoutJsx, processed]); + }, + }); }, options: { diff --git a/lib/configure-jsx-transform.js b/lib/configure-jsx-transform.js new file mode 100644 index 0000000..d20db1e --- /dev/null +++ b/lib/configure-jsx-transform.js @@ -0,0 +1,43 @@ +const VersionChecker = require('ember-cli-version-checker'); + +function requireTransform(transformName) { + return require.resolve(transformName); +} + +function hasPlugin(plugins, name) { + for (const maybePlugin of plugins) { + const plugin = Array.isArray(maybePlugin) ? maybePlugin[0] : maybePlugin; + const pluginName = typeof plugin === 'string' ? plugin : plugin.name; + + if (pluginName === name) { + return true; + } + } + + return false; +} + +module.exports = function configureJsxTransform(parent) { + const options = (parent.options = parent.options || {}); + + const checker = new VersionChecker(parent).for('ember-cli-babel', 'npm'); + + options.babel = options.babel || {}; + options.babel.plugins = options.babel.plugins || []; + + if (checker.satisfies('^6.0.0-beta.1')) { + if (!hasPlugin(options.babel.plugins, 'babel-plugin-transform-react-jsx')) { + options.babel.plugins.push( + requireTransform('babel-plugin-transform-react-jsx') + ); + } + } else if (checker.gte('7.0.0')) { + if ( + !hasPlugin(options.babel.plugins, '@babel/plugin-transform-react-jsx') + ) { + options.babel.plugins.push( + requireTransform('@babel/plugin-transform-react-jsx') + ); + } + } +}; diff --git a/package.json b/package.json index 88450dc..d0fe9b0 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "author": "alex@altschool.com", "license": "MIT", "devDependencies": { + "babel-eslint": "10.0.1", "broccoli-asset-rev": "^2.4.5", "ember-ajax": "^3.0.0", "ember-cli": "~2.15.1", @@ -59,10 +60,16 @@ "husky": "^0.14.3", "lint-staged": "^5.0.0", "loader.js": "^4.2.3", - "prettier": "^1.9.2" + "prettier": "^1.9.2", + "babel-plugin-transform-class-properties": "6.24.1", + "babel-plugin-transform-object-rest-spread": "6.26.0", + "broccoli-funnel": "2.0.1", + "broccoli-merge-trees": "3.0.1", + "ember-cli-version-checker": "^2.1.2" }, "dependencies": { - "broccoli-react": "^0.8.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "babel-plugin-transform-react-jsx": "^6.24.1", "ember-auto-import": "^1.0.1", "ember-cli-babel": "^6.3.0", "react": "^15.5.4 || ^16.0.0", diff --git a/tests/dummy/app/components/babel-features.jsx b/tests/dummy/app/components/babel-features.jsx new file mode 100644 index 0000000..5d86b35 --- /dev/null +++ b/tests/dummy/app/components/babel-features.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +class State { + message = 'supports class properties'; +} + +export default function BabelFeatures() { + let attrs = { 'data-test': 'allows-object-spread' }; + let s = new State(); + return
{s.message}
; +} diff --git a/tests/integration/components/react-component-test.js b/tests/integration/components/react-component-test.js index 2148d28..4e11138 100644 --- a/tests/integration/components/react-component-test.js +++ b/tests/integration/components/react-component-test.js @@ -20,6 +20,13 @@ describeComponent( expect(this.$()).to.have.length(1); }); + it('uses babel features', function() { + this.render(hbs`{{react-component "babel-features"}}`); + expect(this.$('[data-test="allows-object-spread"]').text()).to.equal( + 'supports class properties' + ); + }); + it.skip('throws error when no component found', function() { expect(() => { this.render(hbs`{{react-component "missing-component"}}`);