Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
109 changes: 96 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
**Experimental Addon**

This was built as a prototype to evaluate using react inside of our Ember apps. We are not yet using it in production. PRs and constructive questions and comments via [GitHub issues](https://github.com/AltSchool/ember-cli-react/issues/new) are highly encouraged.
This was built as a prototype to evaluate using React inside of our Ember apps.
We are not yet using it in production. PRs and constructive questions and
comments via [GitHub
issues](https://github.com/AltSchool/ember-cli-react/issues/new) are highly
encouraged.

# ember-cli-react

Expand All @@ -26,15 +30,16 @@ yarn add --dev ember-cli-react
ember generate ember-cli-react
```

**NOTE**:
`ember-cli-react` relies on a custom resolver to discover components. If you have
installed `ember-cli-react` with the standard way then you should be fine. Otherwise, you will need to manually update the first line of `app/resolver.js` to `import Resolver from 'ember-cli-react/resolver';`.
**NOTE**: `ember-cli-react` relies on a custom resolver to discover components.
If you have installed `ember-cli-react` with the standard way then you should be
fine. Otherwise, you will need to manually update the first line of
`app/resolver.js` to `import Resolver from 'ember-cli-react/resolver';`.

## Usage

Write your React component as usual:

```javascript
```jsx
// app/components/say-hi.jsx
import React from 'react';

Expand All @@ -49,11 +54,13 @@ Then render your component in a handlebars template:
{{say-hi name="Alex"}}
```

**NOTE**: Currently, `ember-cli-react` recognizes React components with `.jsx` extension only.
**NOTE**: Currently, `ember-cli-react` recognizes React components with `.jsx`
extension only.

## Block Form

Your React component can be used in block form to allow composition with existing Ember or React components.
Your React component can be used in block form to allow composition with
existing Ember or React components.

```handlebars
{{#react-panel}}
Expand All @@ -63,8 +70,9 @@ Your React component can be used in block form to allow composition with existin

The children of `react-panel` will be populated to `props.children`.

Note that if the children contains mutating structure (e.g. `{{if}}`, `{{each}}`),
you need to wrap them in a stable tag to work around [this Glimmer issue](https://github.com/yapplabs/ember-wormhole/issues/66#issuecomment-263575168).
Note that if the children contains mutating structure (e.g. `{{if}}`,
`{{each}}`), you need to wrap them in a stable tag to work around [this Glimmer
issue](https://github.com/yapplabs/ember-wormhole/issues/66#issuecomment-263575168).

```handlebars
{{#react-panel}}
Expand All @@ -78,8 +86,80 @@ you need to wrap them in a stable tag to work around [this Glimmer issue](https:
{{/react-panel}}
```

Although this is possible, block form should be used as a tool to migrate Ember to React
without the hard requirement to start with leaf components. It is highly recommended to have clean React component tree whenever possible for best performance.
Although this is possible, block form should be used as a tool to migrate Ember
to React without the hard requirement to start with leaf components. It is
highly recommended to have clean React component tree whenever possible for best
performance.

## Using File Name Convention for React
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section is the real content addition

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a good explanation of why, but doesn't really make it clear what ember-cli-react is providing in terms of file name support. I think it should make it clear that 1) you can name your jsx files in PascalCase and 2) either import them in templates using standard dasherized syntax or refer to them there using PascalCase with the aid of react-component.


React is unopinionated with file name convention. However, the majority of the
community has still developed some conventions over time.

For React component files, the widely adopted convention is PascalCase,
including
[Airbnb](https://github.com/airbnb/javascript/tree/master/react#naming). So we
have added support for this convention.

In short, you can name your JSX files in `PascalCase`, in addition to
`snake-case`.

```handlebars
{{!-- Both `user-avatar.jsx` and `UserAvatar.jsx` work --}}
{{user-avatar}}
```

### Rendering in Template

When using the `react-component` component, referencing your React components
with `PascalCase` is also supported. However, due to the "at least one dash"
policy, it won't work if the component name is used directly.

```handlebars
{{!-- OK! --}}
{{react-component "user-avatar"}}

{{!-- OK! --}}
{{react-component "UserAvatar"}}

{{!-- OK! --}}
{{user-avatar}}

{{!-- NOT OK! --}}
{{UserAvatar}}
```

### Single-worded Component

Ember requires at least a dash for component names. So single-worded component
(e.g. `Avatar`) cannot be used directly in Handlebars. However, you can still
use single-worded component with `react-component` component.

```handlebars
{{!-- This won't work because Ember requires a dash for component --}}
{{avatar}}

{{!-- This works --}}
{{react-component 'Avatar'}}
```

### React Components are Prioritised

Whenever there is a conflict, component files with React-style convention will
be used.

Examples:

- When both `SameName.jsx` and `same-name.jsx` exist, `SameName.jsx` will be
used
- When both `SameName.jsx` and `same-name.js` (Ember) exist, `SameName.jsx` will
be used

#### Known issue

If an Ember component and a React component has exactly the same name but different extension (`same-name.js` and
`same-name.jsx`), the file with `.js` extension will be overwritten with the
output of `same-name.jsx`. We are still looking at ways to resolve this.

## Mini Todo List Example

Expand Down Expand Up @@ -176,6 +256,9 @@ export default class TodoItem extends React.Component {

## What's Missing

There is no React `link-to` equivalent for linking to Ember routes inside of your React code. Instead pass action handlers that call `transitionTo` from an Ember route or component.
There is no React `link-to` equivalent for linking to Ember routes inside of
your React code. Instead pass action handlers that call `transitionTo` from an
Ember route or component.

In order to create minified production builds of React you must set `NODE_ENV=production`.
In order to create minified production builds of React you must set
`NODE_ENV=production`.
36 changes: 34 additions & 2 deletions addon/resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,58 @@ import ReactComponent from 'ember-cli-react/components/react-component';
const { get } = Ember;

export default Resolver.extend({
// `resolveComponent` is triggered when rendering a component in template.
// For example, having `{{foo-bar}}` in a template will trigger `resolveComponent`
// with the name full name of `component:foo-bar`.
resolveComponent(parsedName) {
const result = this.resolveOther(parsedName);
// First try to resolve with React-styled file name (e.g. SayHi).
// If nothing is found, try again with original convention via `resolveOther`.
let result =
this._resolveReactStyleFile(parsedName) || this.resolveOther(parsedName);

// If there is no result found after all, return nothing
if (!result) {
return;
}

// If there is an Ember component found, return it.
// This includes the `react-component` Ember component.
if (get(result, 'isComponentFactory')) {
return result;
} else {
// This enables using React Components directly in template
return ReactComponent.extend({
reactComponent: result,
});
}
},

// This resolver method is defined when we try to lookup from `react-component`.
// We create a new namespace `react-component:the-component` for them.
resolveReactComponent(parsedName) {
parsedName.type = 'component';
const result = this.resolveOther(parsedName);
const result =
this._resolveReactStyleFile(parsedName) || this.resolveOther(parsedName);
parsedName.type = 'react-component';
return result;
},

// This resolver method attempt to find a file with React-style file name.
// A React-style file name is in PascalCase.
// This is made a private method to prevent creation of "react-style-file:*"
// factory.
_resolveReactStyleFile(parsedName) {
const originalName = parsedName.fullNameWithoutType;

// Convert the compnent name while preserving namespaces
const parts = originalName.split('/');
parts[parts.length - 1] = Ember.String.classify(parts[parts.length - 1]);
const newName = parts.join('/');

const parsedNameWithPascalCase = Object.assign({}, parsedName, {
fullNameWithoutType: newName,
});
const result = this.resolveOther(parsedNameWithPascalCase);
return result;
},
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"eslint --fix",
"git add"
],
"+(*.{json,css}|.prettierrc|.watchmanconfig)": [
"+(*.{json,css,md}|.prettierrc|.watchmanconfig)": [
"prettier --write",
"git add"
]
Expand Down
7 changes: 7 additions & 0 deletions tests/dummy/app/components/Card.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react';

const Card = () => {
return <span>I am a Card component, I have no dash!</span>;
};

export default Card;
7 changes: 7 additions & 0 deletions tests/dummy/app/components/ReactStyleFileName.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react';

const ReactStyleFileName = () => {
return <span>My file name is ReactStyleFileName</span>;
};

export default ReactStyleFileName;
7 changes: 7 additions & 0 deletions tests/dummy/app/components/SameNameDifferentCaseMixed.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react';

const SameNameDifferentCaseMixed = () => {
return <span>My file name is "SameNameDifferentCaseMixed.jsx"</span>;
};

export default SameNameDifferentCaseMixed;
7 changes: 7 additions & 0 deletions tests/dummy/app/components/SameNameJsx.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react';

const SameNameJsxWithPascalCase = () => {
return <span>My file name is "SameNameJsx.jsx"</span>;
};

export default SameNameJsxWithPascalCase;
7 changes: 7 additions & 0 deletions tests/dummy/app/components/namespace/InsideNamespace.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react';

const InsideNamespace = () => {
return <span>I am inside a namespace!</span>;
};

export default InsideNamespace;
3 changes: 3 additions & 0 deletions tests/dummy/app/components/same-name-different-case-mixed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Ember from 'ember';

export default Ember.Component.extend();
7 changes: 7 additions & 0 deletions tests/dummy/app/components/same-name-jsx.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react';

const SameNameJsxWithSnakeCase = () => {
return <span>My file name is "same-name-jsx.jsx"</span>;
};

export default SameNameJsxWithSnakeCase;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
My file name is "same-name-different-case-mixed.js"
Loading