Skip to content

Commit

Permalink
Add decorator for creating the shim class
Browse files Browse the repository at this point in the history
  • Loading branch information
alexlafroscia committed Mar 24, 2018
1 parent e70353d commit 27f2ab6
Show file tree
Hide file tree
Showing 15 changed files with 469 additions and 61 deletions.
22 changes: 19 additions & 3 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
module.exports = {
root: true,
parser: 'babel-eslint',
parserOptions: {
ecmaVersion: 2017,
sourceType: 'module'
sourceType: 'module',
ecmaFeatures: {
jsx: true
}
},
plugins: [
'ember'
'babel',
'ember',
'react',
'prettier'
],
extends: [
'eslint:recommended',
'plugin:ember/recommended'
'plugin:ember/recommended',
'prettier'
],
env: {
browser: true
},
rules: {
'prettier/prettier': ['error', {
singleQuote: true
}],

'react/jsx-uses-react': 'error',
'react/jsx-uses-vars': 'error',

'ember/no-attrs-in-components': 'off'
},
overrides: [
// node files
Expand Down
64 changes: 44 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,74 @@
ember-cli-react
==============================================================================
> Consume React components in Ember ???
[Short description of the addon.]
This addon is a proof-of-concept for an approach to rendering React components in Ember. It is almost entirely inspired by [a blog post][blog-post] by [Sivakumar Kailasam][sivakumar], from which the general idea was mostly borrowed.

Installation
------------------------------------------------------------------------------

```
ember install ember-cli-react
yarn add -D alexlafroscia/ember-cli-react react react-dom
```

Note: `react` and `react-dom` are considered peer dependencies of this addon. You should install them in addition to the addon itself.


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

[Longer description of how to use the addon in apps.]
This addon provides an ES6 class decorator that allows a React element to be rendered in Ember.

As an example, you can create a component like this:

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

### Installation
```javascript
// app/components/my-react-component.js
import React from 'react';
import WithEmberSupport from 'ember-cli-react';

@WithEmberSupport
export default class extends React.Component {
render() {
const { name } = this.props;

return (
<p>Hello, {name}</p>
);
}
}
```

And render it like this:

* `git clone <repository-url>`
* `cd ember-cli-react`
* `npm install`
```handlebars
{{my-react-component name='Alex'}}
```

That would create a component that renders `Hello, Alex`.

### Linting
Got'chas
------------------------------------------------------------------------------

* `npm run lint:js`
* `npm run lint:js -- --fix`
* Any time a property on `my-react-component` changes, it will blow away the React component entirely and re-render it. You will lose any temporal state.

### Running tests
What all is this addon doing?
------------------------------------------------------------------------------

* `ember test` – Runs the test suite on the current Ember version
* `ember test --server` – Runs the test suite in "watch mode"
* `npm test` – Runs `ember try:each` to test your addon against multiple Ember versions
* Provides imports for `react` and `react-dom`
* Hooks up a bunch of necessary `babel` transforms
* Includes the decorator for creating a React-Ember-component Frankenstein-ian monster class that does the "heavy lifting" to bridge the two frameworks

### Running the dummy application
Is this production ready?
------------------------------------------------------------------------------

* `ember serve`
* Visit the dummy application at [http://localhost:4200](http://localhost:4200).
O god, please do not use this

For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/).

License
------------------------------------------------------------------------------

This project is licensed under the [MIT License](LICENSE.md).

[blog-post]: https://medium.com/@sivakumar_k/using-react-components-in-your-ember-app-8f7805d409b0
[sivakumar]: https://github.com/sivakumar-kailasam
52 changes: 52 additions & 0 deletions addon/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Component from '@ember/component';
import { get } from '@ember/object';
import { schedule } from '@ember/runloop';
import React from 'react';
import ReactDOM from 'react-dom';

export default function WithEmberSupport(Klass) {
return class extends Component {
constructor() {
super();

for (const key in Klass.prototype) {
this[key] = Klass.prototype[key];
}
}

_getPropsForReact() {
return Object.keys(this.attrs).reduce((acc, key) => {
const value = get(this, key);

acc[key] = value;

return acc;
}, {});
}

_mountElement() {
this._reactElement = ReactDOM.render(
<Klass {...this._getPropsForReact()} />,
this.element
);
}

didUpdateAttrs() {
schedule('render', () => {
this._mountElement();
});
}

didInsertElement() {
super.didInsertElement(...arguments);

this._mountElement();
}

willDestroyElement() {
ReactDOM.unmountComponentAtNode(this.element);

super.willDestroyElement(...arguments);
}
};
}
4 changes: 2 additions & 2 deletions config/ember-try.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ module.exports = function() {
return Promise.all([
getChannelURL('release'),
getChannelURL('beta'),
getChannelURL('canary'),
]).then((urls) => {
getChannelURL('canary')
]).then(urls => {
return {
scenarios: [
{
Expand Down
2 changes: 1 addition & 1 deletion config/environment.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use strict';

module.exports = function(/* environment, appConfig */) {
return { };
return {};
};
45 changes: 32 additions & 13 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
module.exports = {
name: 'ember-cli-react',

options: {
babel: {
plugins: ['transform-class-properties', 'transform-react-jsx']
}
},

appOptions() {
return (
(this.parent && this.parent.options) || (this.app && this.app.options)
Expand All @@ -12,24 +18,37 @@ module.exports = {
included() {
this._super.included.apply(this, arguments);

this.import({
development: 'node_modules/react/umd/react.development.js',
production: 'node_modules/react/umd/react.production.min.js'
}, {
using: [{ transformation: 'amd', as: 'react' }]
});
this.import(
{
development: 'node_modules/react/umd/react.development.js',
production: 'node_modules/react/umd/react.production.min.js'
},
{
using: [{ transformation: 'amd', as: 'react' }]
}
);

this.import({
development: 'node_modules/react-dom/umd/react-dom.development.js',
production: 'node_modules/react-dom/umd/react-dom.production.min.js'
}, {
using: [{ transformation: 'amd', as: 'react-dom' }]
});
this.import(
{
development: 'node_modules/react-dom/umd/react-dom.development.js',
production: 'node_modules/react-dom/umd/react-dom.production.min.js'
},
{
using: [{ transformation: 'amd', as: 'react-dom' }]
}
);

const opts = this.appOptions();
opts.babel = opts.babel || {};
opts.babel.plugins = opts.babel.plugins || [];

opts.babel.plugins.push(['transform-class-properties', 'transform-react-jsx']);
const existingPlugins = opts.babel.plugins || [];

opts.babel.plugins = [
...existingPlugins,
'transform-decorators-legacy',
'transform-class-properties',
'transform-react-jsx'
];
}
};
13 changes: 10 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,18 @@
},
"dependencies": {
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-react-jsx": "^6.24.1",
"ember-cli-babel": "^6.6.0"
"ember-cli-babel": "^6.6.0",
"ember-cli-htmlbars": "^2.0.1"
},
"devDependencies": {
"babel-eslint": "^8.2.2",
"broccoli-asset-rev": "^2.4.5",
"ember-ajax": "^3.0.0",
"ember-cli": "~3.0.2",
"ember-cli-dependency-checker": "^2.0.0",
"ember-cli-eslint": "^4.2.1",
"ember-cli-htmlbars": "^2.0.1",
"ember-cli-htmlbars-inline-precompile": "^1.0.0",
"ember-cli-inject-live-reload": "^1.4.1",
"ember-cli-qunit": "^4.1.1",
Expand All @@ -48,14 +50,19 @@
"ember-source": "~3.0.0",
"ember-source-channel-url": "^1.0.1",
"ember-try": "^0.2.23",
"eslint-config-prettier": "^2.9.0",
"eslint-plugin-babel": "^4.1.2",
"eslint-plugin-ember": "^5.0.0",
"eslint-plugin-node": "^5.2.1",
"eslint-plugin-prettier": "^2.6.0",
"eslint-plugin-react": "^7.7.0",
"loader.js": "^4.2.3",
"prettier": "^1.11.1",
"react": "^16.2.0",
"react-dom": "^16.2.0"
},
"engines": {
"node": "^4.5 || 6.* || >= 7.*"
"node": "6.* || >= 7.*"
},
"ember-addon": {
"configPath": "tests/dummy/config"
Expand Down
8 changes: 2 additions & 6 deletions testem.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
module.exports = {
test_page: 'tests/index.html?hidepassed',
disable_watching: true,
launch_in_ci: [
'Chrome'
],
launch_in_dev: [
'Chrome'
],
launch_in_ci: ['Chrome'],
launch_in_dev: ['Chrome'],
browser_args: {
Chrome: {
mode: 'ci',
Expand Down
9 changes: 9 additions & 0 deletions tests/dummy/app/components/basic-component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import WithEmberSupport from 'ember-cli-react';

@WithEmberSupport
export default class FromMixin extends React.Component {
render() {
return <h1>Hello from React</h1>;
}
}
11 changes: 11 additions & 0 deletions tests/dummy/app/components/with-properties.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import WithEmberSupport from 'ember-cli-react';

@WithEmberSupport
export default class WithProperties extends React.Component {
render() {
const { foo } = this.props;

return <p>foo equals {foo}</p>;
}
}
3 changes: 1 addition & 2 deletions tests/dummy/app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ const Router = EmberRouter.extend({
rootURL: config.rootURL
});

Router.map(function() {
});
Router.map(function() {});

export default Router;
14 changes: 12 additions & 2 deletions tests/dummy/app/templates/application.hbs
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
<h2 id="title">Welcome to Ember</h2>
<h2 id="title">Hello from Ember</h2>

{{outlet}}
<button {{action (mut showReactComponent) true}}>
Show react component
</button>

<button {{action (mut showReactComponent) false}}>
Hide react component
</button>

{{#if showReactComponent}}
{{from-mixin}}
{{/if}}
Empty file.
Loading

0 comments on commit 27f2ab6

Please sign in to comment.