Skip to content

Commit

Permalink
First version
Browse files Browse the repository at this point in the history
  • Loading branch information
gregberge committed Jun 23, 2017
1 parent 007907b commit 3572df3
Show file tree
Hide file tree
Showing 31 changed files with 5,703 additions and 0 deletions.
11 changes: 11 additions & 0 deletions .babelrc
@@ -0,0 +1,11 @@
{
"plugins": [
["transform-class-properties", { "loose": true }],
"transform-object-rest-spread",
"dynamic-import-node"
],
"presets": [
["env", { "loose": true }],
"react"
],
}
3 changes: 3 additions & 0 deletions .eslintignore
@@ -0,0 +1,3 @@
node_modules/
/lib/
/coverage/
11 changes: 11 additions & 0 deletions .eslintrc.json
@@ -0,0 +1,11 @@
{
"parser": "babel-eslint",
"extends": ["airbnb", "prettier"],
"env": {
"jest": true
},
"rules": {
"react/jsx-filename-extension": ["error", {"extensions": [".js"]}],
"react/jsx-wrap-multilines": "off"
}
}
3 changes: 3 additions & 0 deletions .gitignore
@@ -0,0 +1,3 @@
node_modules/
/lib/
/coverage/
7 changes: 7 additions & 0 deletions .mversionrc
@@ -0,0 +1,7 @@
{
"scripts": {
"precommit": "yarn build && git add -A",
"postcommit": "git push && git push --tags",
"postupdate": "npm publish lib && conventional-github-releaser --preset angular"
}
}
3 changes: 3 additions & 0 deletions .npmignore
@@ -0,0 +1,3 @@
/*
!/lib/*.js
*.test.js
1 change: 1 addition & 0 deletions .nvmrc
@@ -0,0 +1 @@
8
3 changes: 3 additions & 0 deletions .travis.yml
@@ -0,0 +1,3 @@
language: node_js
node_js:
- "node"
7 changes: 7 additions & 0 deletions LICENSE
@@ -0,0 +1,7 @@
Copyright 2017 Smooth Code

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
289 changes: 289 additions & 0 deletions README.md
@@ -0,0 +1,289 @@
# loadable-components

[![Build Status](https://travis-ci.org/smooth-code/loadable-components.svg?branch=master)](https://travis-ci.org/smooth-code/loadable-components)
[![codecov](https://codecov.io/gh/smooth-code/loadable-components/branch/master/graph/badge.svg)](https://codecov.io/gh/smooth-code/loadable-components)

React code splitting made easy. Split your code without stress.

```sh
npm install loadable-components
```

Webpack permits us to use easily split our code using dynamic `import` syntax.
`loadable-components` makes it possible to use it with React components. It is compatible with react-router and server side rendering. The API is designed to be as simple as possible to avoid useless complexity and boilerplate.

## Getting started

```js
// Routes.js
export const Home = loadable(() => import('./Home'))
export const About = loadable(() => import('./About'))
export const Contact = loadable(() => import('./Contact'))
```

```js
// App.js
import React from 'react'
import { Route } from 'react-router'
import * as Routes from './Routes'

export default () =>
<div>
<Route exact path="/" component={Routes.Home} />
<Route path="/about" component={Routes.About} />
<Route path="/contact" component={Routes.Contact} />
</div>
```

### Custom loading

It's possible to add a custom loading component, by default it will render nothing:

```js
export const Home = loadable(() => import('./Home'), {
LoadingComponent: (props) => <div>Loading...</div>,
})
```

### Error handling

You can configure the component rendered when an error occurs during loading:

```js
export const Home = loadable(() => import('./Home'), {
ErrorComponent: ({ error, props }) => <div>Oups an error occurs.</div>,
})
```

### Prefetching

To enhance the user you can fetch routes before they are requested by the user.

#### Prefetch on route loading

```js
import React from 'react'
import { Contact } from './Routes'

Contact.load()

export default () => <div>Hello</div>
```

#### Prefetch on hover

```js
import React from 'react'
import { Contact } from './Routes'

export default () =>
<div>
<Link
<Link to="/contact" onHover={Contact.load}>Contact</Link>
</div>
```

### Server-side rendering

```js
// main.js
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import { loadComponents } from 'loadable-components'
import App from './App'

// Load all components needed before starting rendering
loadComponents().then(() => {
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('main'),
)
})
```

```js
// App.js
import React from 'react'
import { Home } from './Routes'

const App = () =>
<div>
<Route exact path="/" component={Home} />
</div>
```

```js
// Routes.js
import loadable from 'loadable-components'

export const Home = loadable(() => import('client/Home'))
```

```js
// server.js
import React from 'react'
import { renderToString } from 'react-dom/server'
import { StaticRouter } from 'react-router'
import { getLoadableState } from 'loadable-components/server'
import App from './App'

const app = (
<StaticRouter>
<App />
</StaticRouter>
)

// Extract loadable state from application tree
getLoadableState(app).then(loadableState => {
const html = renderToString(<YourApp />)
// Insert style tag into page
const page = `
<!doctype html>
<html>
<head></head>
<body>
<div id="main">${html}</div>
${loadableState.getScriptTag()}
</body>
</html>
`
})
```

## API Reference

### loadable

This is the default export. It's a factory used to create a loadable component. Props are passed to the loaded component.

### Arguments

1. `getComponent` _(Function)_: Function to load component asynchronously.
2. `options` _(Object)_: Facultative options to configure component behavior.

### options
1. `ErrorComponent` _(ReactComponent)_: Component rendered when an error occurs, take two props: `error` and `props`.
2. `LoadingComponent` _(ReactComponent)_: Component rendered during loading, take the same props from loadable component.

```js
import loadable from 'loadable-components'

const MyLoadableComponent = loadable(() => import('./MyComponent'), {
ErrorComponent: ({ error }) => <div>{error.message}</div>,
LoadingComponent: () => <div>Loading...</div>,
})
```

### loadComponents

This method is only required if you use server-side rendering. It loads components used in the page that has been rendered server-side.

```js
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import { loadComponents } from 'loadable-components'
import App from './App'

// Load all components needed before starting rendering
loadComponents().then(() => {
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('main'),
)
})
```

### getLoadableState

This method is only required if you use server-side rendering. It loads components recursively and extract a loadable state from a React tree.

```js
import React from 'react'
import { renderToString } from 'react-dom/server'
import { StaticRouter } from 'react-router'
import { getLoadableState } from 'loadable-components/server'
import App from './App'

const app = (
<StaticRouter>
<App />
</StaticRouter>
)

// Extract loadable state from application tree
getLoadableState(app).then(loadableState => {
const html = renderToString(<YourApp />)
// Insert style tag into page
const page = `
<!doctype html>
<html>
<head></head>
<body>
<div id="main">${html}</div>
${loadableState.getScriptTag()}
</body>
</html>
`
})
```

A loadable state has two methods to extract state:

- `loadableState.getScriptTag()`: Returns a string representing a script tag.
- `loadableState.getScriptElement()`: Returns a React element.

## Interoperability

You can implement a loadable component by your own. To do it you have to add `LOADABLE` Symbol to your component:

```js
import React from 'react'
import { LOADABLE } from 'loadable-components'

class ComponentWithTranslations extends React.Component {
// Required
static componentId = 'custom-loadable'
static async load = () => {
const response = await fetch('/translations.json')
const translations = await response.json()
ComponentWithTranslations.translations = translations
return translations
}

state = { translations: ComponentWithTranslations.translations }

componentWillMount() {
ComponentWithTranslations[LOADABLE].load()
.then(translations => this.setState({ translations }))
}

render() {
const { translations = { hello = 'hello' } } = this.props;

return <div>{hello}</div>
}
}

ComponentWithTranslations[LOADABLE] = () => ({
componentId: 'custom-loadable',
load: async () => {
const response = await fetch('/translations.json')
const translations = await response.json()
ComponentWithTranslations.translations = translations
}
})
```

## Inspirations

- API inspired by [styled-components](https://github.com/styled-components/styled-components)
- React tree traversing from [react-apollo](https://github.com/apollographql/react-apollo)
- Loadable components inspired by [react-loadable](https://github.com/thejameskyle/react-loadable)

## License MIT

0 comments on commit 3572df3

Please sign in to comment.