Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs(project): add project docs (#4)
- Loading branch information
Showing
9 changed files
with
269 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
**/node_modules/** | ||
packages/universal-components/storybook-static |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,43 @@ | ||
# universal-components-example | ||
An example setup for universal components | ||
# Universal Components Example | ||
An example setup for building/testing/deploying universal components with React | ||
|
||
## Folder Structure | ||
This project uses [`Lerna`](https://github.com/lerna/lerna) to make managing multiple NPM packages easier. Often times you will end up with other packages related to your universal components package and using Lerna makes managing the inter-dependencies easier. | ||
|
||
- `packages`: This directory holds the actual NPM packages you want to publish via Lerna (currently only the [universal-components](./packages/universal-components) package. | ||
- `examples`: This directory holds a web and a native example app. | ||
|
||
## Available Scripts | ||
|
||
### lerna bootstrap | ||
Hoists shared dependencies from any packges in `packages` directory. Also creates symlinks for inter-dependent packages in `packages` directory | ||
|
||
### lerna publish | ||
Publishes new tags and package versions for any packages in `packages` directory that have changed since last publish | ||
|
||
## Running the Examples | ||
|
||
### Web | ||
|
||
This app was created using `create-react-app`. And although CRA supports using `react-native-web` out of the box, we need to eject so that we can [update Webpack config](./examples/web/config/webpack.config.dev.js) to also parse the universal components package (we must also do the same in [prod Wepback config](./examples/web/config/webpack.config.prod.js)). | ||
|
||
You can see the universal component being included [here](./examples/web/src/App.js). | ||
|
||
``` | ||
cd examples/web | ||
yarn install | ||
yarn start | ||
``` | ||
|
||
### Native | ||
|
||
This app was created using `create-react-native-app`. There were no special setup steps required. | ||
|
||
You can see the universal component being included [here](./examples/native/App.js). | ||
|
||
``` | ||
cd examples/native | ||
yarn install | ||
yarn start | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,3 +7,6 @@ | |
|
||
[libs] | ||
flow-typed | ||
|
||
[options] | ||
module.name_mapper='\(react-native\)' -> 'react-native-web' |
52 changes: 52 additions & 0 deletions
52
packages/universal-components/.storybook/webpack.config.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
const path = require('path'); | ||
const webpack = require('webpack'); | ||
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin'); | ||
const MinifyPlugin = require('babel-minify-webpack-plugin'); | ||
|
||
const genDefaultConfig = require('@storybook/react/dist/server/config/defaults/webpack.config.js'); | ||
|
||
const DEV = process.env.NODE_ENV !== 'production'; | ||
|
||
const prodPlugins = [ | ||
new webpack.DefinePlugin({ | ||
// prettier-ignore | ||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'), | ||
'process.env.__REACT_NATIVE_DEBUG_ENABLED__': DEV, | ||
}), | ||
new webpack.optimize.OccurrenceOrderPlugin(), | ||
new MinifyPlugin(), | ||
]; | ||
|
||
module.exports = (baseConfig, env) => { | ||
const config = genDefaultConfig(baseConfig, env); | ||
|
||
const defaultPlugins = config.plugins.concat([ | ||
new FriendlyErrorsWebpackPlugin(), | ||
]); | ||
|
||
const overwrite = { | ||
devtool: 'inline-source-map', | ||
module: { | ||
rules: [ | ||
{ | ||
test: /\.js$/, | ||
exclude: /node_modules/, | ||
loader: 'babel-loader', | ||
query: { cacheDirectory: true }, | ||
}, | ||
{ | ||
test: /\.css$/, | ||
use: [{ loader: 'style-loader' }, { loader: 'css-loader' }], | ||
}, | ||
{ | ||
test: /\.(gif|jpe?g|png|svg|otf|ttf)$/, | ||
loader: 'url-loader', | ||
query: { name: '[name].[ext]' }, | ||
}, | ||
], | ||
}, | ||
plugins: DEV ? defaultPlugins : prodPlugins, | ||
}; | ||
|
||
return Object.assign(config, overwrite); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
# Universal Components Package | ||
|
||
## What are universal components? | ||
|
||
Universal components are React components that can render anywhere. They are platform agnostic, which means you can use them on the web and native without changing any lines of code. | ||
|
||
Why architect your components this way? The main benefit is code reuse. By building your components with React Native primitives, you only have to write them once. Since other platforms such as VR & Sketch also share RN primitives, you can eventually expand universal components to include those platforms as well (although some issues such as cross-platform SVGs are still getting ironed out). | ||
|
||
To achieve a fully universal component library, we use [react-native-web](https://github.com/necolas/react-native-web). RNW sits as a layer on top of DOM primitives; for example, `<View />` will render to a `<div/>` on the web. RNW has nearly complete feature parity with RN, so you can be confident that almost any code that works with RN will also work with RNW. Check the [RNW explorer](https://necolas.github.io/react-native-web/storybook/) for a comprehensive list of supported components & APIs. | ||
|
||
## Workflow | ||
|
||
Components can be developed in isolation with [Storybook](https://storybook.js.org/) to eliminate differences between projects. This project uses the web version of Storybook to make sure that all universal components will render on the web. | ||
|
||
## Folder structure: | ||
|
||
- `components/`: Where all your universal components live | ||
- `.storybook`: Storybook specific configuration | ||
- `storybook-static`: Where storybook exports builds to | ||
- `flow-typed/`: [Shared Flow types](https://github.com/flowtype/flow-typed) | ||
|
||
Components are exported in `index.js` and published to an NPM package (`@<YOUR_NPM_USERNAME>/universal-components`) so they can be consumed by multiple projects. In order to coordinate publishing multiple packages in this repo, you use [Lerna](https://lernajs.io/). Run `lerna publish` from root of project to publish a version of this package. Then, you can upgrade the package within the application (`yarn upgrade @<YOUR_NPM_USERNAME>/universal-components`) and import the components to use them. | ||
|
||
## Available Scripts | ||
|
||
### yarn storybook | ||
Starts up storybook for development of components | ||
|
||
### yarn build | ||
Builds the production version of storybook | ||
|
||
### yarn deploy | ||
Deploys the production build of storybook to [`surge.sh`](https://surge.sh) | ||
|
||
## Styling | ||
|
||
For styling, the `StyleSheet` API is included in RN so you can write your styles as JS objects. RNW supports the `StyleSheet` API via CSS modules. Don't be alarmed if you open up dev tools and see a huge string of ugly looking class names attached to your DOM elements - this is RNW's way of memoizing styles for fast performance compared to other CSS in JS solutions. | ||
|
||
Since the RNW styling API mirrors RN's, it follows the default styling properties of [Yoga](https://facebook.github.io/yoga/docs/learn-more/), Facebook's cross-platform layout engine. The main point to remember about Yoga is that Flexbox is enabled by default with `flexDirection: column`. | ||
|
||
## Platform specific code | ||
|
||
Sometimes, you might need to account for slight differences between platforms in your components. You can either use the Platform API or platform extensions to accomplish this. | ||
|
||
### Platform API: Good for styling one-offs | ||
```javascript | ||
import { Platform } from 'react-native'; | ||
|
||
switch (Platform.OS) { | ||
case 'ios': | ||
case 'android': | ||
// do something on native | ||
case 'web': | ||
default: | ||
// do something on web | ||
} | ||
``` | ||
|
||
You can also write it like this: | ||
```javascript | ||
import { Platform } from 'react-native'; | ||
|
||
const styles = StyleSheet.create({ | ||
container: { | ||
flex: 1, | ||
...Platform.select({ | ||
ios: { | ||
backgroundColor: 'red', | ||
}, | ||
android: { | ||
backgroundColor: 'blue', | ||
}, | ||
}), | ||
}, | ||
}); | ||
``` | ||
|
||
### Platform extensions: Good for differences in component implementation | ||
|
||
```javascript | ||
import { Button } from '../components/button' | ||
// will resolve from '../components/button/index.web.js' on web, '../components/button/index.ios.js' on iOS, etc | ||
``` | ||
|
||
This functionality is built into the React Native packager. We've also added the ability to resolve platform extensions on web in the babel config (see [`.babelrc`](./.babelrc) for an example). | ||
|
||
|
||
## Storybook configuration | ||
Storybook configuration can be found in [`./.storybook`](./.storybook). The two key parts to notice are: | ||
- Stories live next to components ([see example](./components/button)) and we load them via `./.storybook/config.js` | ||
- Because we use babel-minify over UglifyJS for minifying we have to use full control mode for Webpack `./.storybook/webpack.config.js` | ||
|
||
## How to use React Native modules on the web | ||
|
||
⚠️ Warning: This is slightly hacky, but if you should do it anyway because standardizing third party modules & component libraries across platforms is important. | ||
|
||
Some examples of RN libraries that can be used on the web include `victory-native`, `react-native-calendars`, and `react-native-vector-icons`. We're able to use these because of RNW's high feature parity with RN, the ability to transpile, and alias with Webpack or Babel. | ||
|
||
### Steps: | ||
|
||
1. After you install the library, you will need to transpile it with Babel because all RN modules are ES6. To do this, add the module name to the RegExp in `babel-loader` in `.storybook/webpack.config.js`: | ||
```javascript | ||
{ | ||
test: /\.js$/, | ||
exclude: /node_modules\/(?!svgs)(?!MODULE_NAME_HERE)/, | ||
loader: 'babel-loader', | ||
query: { cacheDirectory: true }, | ||
}, | ||
``` | ||
2. In some cases, you'll need to create an alias in the Babel config. For example, you alias `svgs` to `react-native-svg` for your cross-platform svg implementation. You'll almost always be aliasing a web library's name to its native counterpart because aliasing is currently harder to execute with the RN packager, [Metro](https://github.com/facebook/metro-bundler). | ||
|
||
Add the alias to `"module-resolver"` plugin in `.babelrc` like this: | ||
```javascript | ||
"alias": { | ||
"react-native": "react-native-web", | ||
"react-native-svg": "svgs", | ||
``` | ||
3. If you're aliasing, you will need to tell Flow how to resolve the module. Add the module name mapper to `.flowconfig`: | ||
``` | ||
module.name_mapper='\(react-native\)' -> 'react-native-web' | ||
module.name_mapper='\(react-native-svg\)' -> 'svgs' | ||
``` | ||
5. If you've made it this far and everything is working, celebrate! 🎉 You can now import your components normally. If you're getting cryptic errors, you might want to reach out to the maintainer of the library. Usually, they're more than happy to offer their help with supporting web. 😊 | ||
## Resources | ||
- [React as a Platform](https://www.youtube.com/watch?v=hNwQPJy-XZY) | ||
- [Write Once, Render Anywhere](http://reactnyc-universal-components.surge.sh/#/) | ||
- [The Road to Universal Components](https://labs.mlssoccer.com/the-road-to-universal-components-at-major-league-soccer-eeb7aac27e6c) |
5 changes: 2 additions & 3 deletions
5
packages/universal-components/components/button/__stories__/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters