docs: add website

This commit is contained in:
Morgane Dubus 2018-12-05 13:36:44 +01:00 committed by Greg Bergé
parent 26dd34478e
commit 428599cdc7
47 changed files with 14816 additions and 405 deletions

View File

@ -3,3 +3,5 @@ node_modules/
dist/
lib/
build/
/website/.cache/
/website/public/

View File

@ -17,4 +17,4 @@ Please provide an example for how this feature would be used.
## Pitch
Why does this feature belong in the SVGR ecosystem?
Why does this feature belong in the Loadable Component ecosystem?

View File

@ -4,4 +4,6 @@ node_modules/
__fixtures__/
CHANGELOG.md
package.json
lerna.json
lerna.json
/website/.cache/
/website/public/

View File

@ -8,7 +8,7 @@ We expect project participants to adhere to our Code of Conduct. Please read [th
## Open Development
All work on SVGR happens directly on [GitHub](/). Both core team members and external contributors send pull requests which go through the same review process.
All work on Loadable Components happens directly on [GitHub](/). Both core team members and external contributors send pull requests which go through the same review process.
### Workflow and Pull Requests

305
README.md
View File

@ -12,32 +12,21 @@
[![DevDependencies](https://img.shields.io/david/dev/smooth-code/loadable-components.svg)](https://david-dm.org/smooth-code/loadable-components?type=dev)
[![Small size](https://img.badgesize.io/https://unpkg.com/@loadable/component/dist/loadable.min.js?compression=gzip)](https://unpkg.com/@loadable/component/dist/loadable.min.js)
```sh
```bash
npm install @loadable/component
```
## Introduction
## [Docs](https://www.smooth-code.com/open-source/loadable-components)
Code splitting is supported out of the box by React using [`React.lazy`](https://reactjs.org/docs/code-splitting.html#reactlazy). So what is the goal of this library?
**See the documentation at [smooth-code.com/open-source/loadable-components](https://www.smooth-code.com/open-source/loadable-components)** for more information about using `loadable components`!
`@loadable/component` pushes the limit of Code splitting, it offers several features:
Quicklinks to some of the most-visited pages:
- 📚 Library splitting
- ⚡️ Prefetching
- 💫 Server Side Rendering
- 🎛 Full dynamic import `` import(`./${value}`) ``
- [**Getting started**](https://www.smooth-code.com/open-source/loadable-components/docs/getting-started/)
- [Comparison with React.lazy](https://www.smooth-code.com/open-source/loadable-components/docs/loadable-vs-react-lazy/)
- [Server Side Rendering](https://www.smooth-code.com/open-source/loadable-components/docs/server-side-rendering/)
## Comparison with React.lazy & react-loadable
| Library | Suspense | SSR | Library splitting | `` import(`./${value}`) `` |
| --------------------- | -------- | --- | ----------------- | -------------------------- |
| `React.lazy` | ✅ | ❌ | ❌ | ❌ |
| `react-loadable` | ❌ | 🔶 | ❌ | ❌ |
| `@loadable/component` | ✅ | ✅ | ✅ | ✅ |
## Getting started
`loadable` lets you render a dynamic import as a regular component.
## Example
```js
import loadable from '@loadable/component'
@ -53,280 +42,8 @@ function MyComponent() {
}
```
### Loading library
## Licence
`loadable.lib` lets you defer the loading of a library. It takes a render props called when the library is loaded.
Licensed under the MIT License, Copyright © 2017-present Smooth Code.
```js
import loadable from '@loadable/component'
const Moment = loadable.lib(() => import('moment'))
function FromNow({ date }) {
return (
<div>
<Moment fallback={date.toLocaleDateString()}>
{({ default: moment }) => moment(date).fromNow()}
</Moment>
</div>
)
}
```
You can also use a `ref`, populated when the library is loaded.
```js
import loadable from '@loadable/component'
const Moment = loadable.lib(() => import('moment'))
class MyComponent {
moment = React.createRef()
handleClick = () => {
if (this.moment.current) {
return alert(this.moment.current.default.format('HH:mm'))
}
}
render() {
return (
<div>
<button onClick={this.handleClick}>What time is it?</button>
<Moment ref={this.moment} />
</div>
)
}
}
```
> You can also pass a function to `ref`, called when the library is loaded.
### Full dynamic import
Webpack accepts [full dynamic imports](https://webpack.js.org/api/module-methods/#import-), you can use them to create a reusable Loadable Component.
```js
import loadable from '@loadable/component'
const AsyncPage = loadable(props => import(`./${props.page}`))
function MyComponent() {
return (
<div>
<AsyncPage page="Home" />
<AsyncPage page="Contact" />
</div>
)
}
```
### Suspense
`@loadable/component` exposes a `lazy` method that acts similarly as `React.lazy` one.
```js
import { lazy } from '@loadable/component'
const OtherComponent = lazy(() => import('./OtherComponent'))
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
)
}
```
> Use `lazy.lib` for libraries.
> ⚠️ Suspense is not yet available for server-side rendering.
### Fallback without Suspense
You can specify a `fallback` in `loadable` options.
```js
const OtherComponent = loadable(() => import('./OtherComponent'), {
fallback: <div>Loading...</div>,
})
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
)
}
```
You can also specify a `fallback` in props:
```js
const OtherComponent = loadable(() => import('./OtherComponent'))
function MyComponent() {
return (
<div>
<OtherComponent fallback={<div>Loading...</div>} />
</div>
)
}
```
### Error boundaries
If the other module fails to load (for example, due to network failure), it will trigger an error. You can handle these errors to show a nice user experience and manage recovery with [Error Boundaries](https://reactjs.org/docs/error-boundaries.html). Once youve created your Error Boundary, you can use it anywhere above your lazy components to display an error state when theres a network error.
```js
import MyErrorBoundary from '/MyErrorBoundary'
const OtherComponent = loadable(() => import('./OtherComponent'))
const AnotherComponent = loadable(() => import('./AnotherComponent'))
const MyComponent = () => (
<div>
<MyErrorBoundary>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</MyErrorBoundary>
</div>
)
```
### Delay
To avoid flashing a loader if the loading is very fast, you could implement a minimum delay. There is no built-in API in `@loadable/component` but you could do it using [`p-min-delay`](https://github.com/sindresorhus/p-min-delay).
```js
import loadable from '@loadable/component'
import pMinDelay from 'p-min-delay'
// Wait a minimum of 200ms before loading home.
export const OtherComponent = loadable(() =>
pMinDelay(import('./OtherComponent'), 200),
)
```
### Timeout
Infinite loading is not good for user experience, to avoid it implementing a timeout is a good workaround. You can do it using a third party module like [`promise-timeout`](https://github.com/building5/promise-timeout):
```js
import loadable from '@loadable/component'
import { timeout } from 'promise-timeout'
// Wait a maximum of 2s before sending an error.
export const OtherComponent = loadable(() =>
timeout(import('./OtherComponent'), 2000),
)
```
### Prefetching
Loadable Components is fully compatible with [webpack hints `webpackPrefetch` and `webpackPreload`](https://webpack.js.org/guides/code-splitting/#prefetching-preloading-modules).
Most of the time, you want to "prefetch" a component, it means it will be loaded when the browser is idle. You can do it by adding `/* webpackPrefetch: true */` inside your import statement.
```js
import loadable from '@loadable/component'
const OtherComponent = loadable(() =>
import(/* webpackPrefetch: true */ './OtherComponent'),
)
```
> You can extract prefetched resources server-side to add `<link rel="prefetch">` in your head.
## [Server side rendering](https://github.com/smooth-code/loadable-components/tree/master/packages/server/README.md)
👉 [See `@loadable/server` documentation](https://github.com/smooth-code/loadable-components/tree/master/packages/server/README.md).
## API
### loadable
Create a loadable component.
| Arguments | Description |
| ------------------ | ---------------------------------------- |
| `loadFn` | The function call to load the component. |
| `options` | Optional options. |
| `options.fallback` | Fallback displayed during the loading. |
```js
import loadable from '@loadable/component'
const OtherComponent = loadable(() => import('./OtherComponent'))
```
### lazy
Create a loadable component "Suspense" ready.
| Arguments | Description |
| --------- | ---------------------------------------- |
| `loadFn` | The function call to load the component. |
```js
import { lazy } from '@loadable/component'
const OtherComponent = lazy(() => import('./OtherComponent'))
```
### LoadableComponent
A component created using `loadable` or `lazy`.
| Props | Description |
| ---------- | ------------------------------------------------- |
| `fallback` | Fallback displayed during the loading. |
| `...` | Props are forwarded as first argument of `loadFn` |
### loadable.lib
Create a loadable library.
| Arguments | Description |
| ------------------ | ---------------------------------------- |
| `loadFn` | The function call to load the component. |
| `options` | Optional options. |
| `options.fallback` | Fallback displayed during the loading. |
```js
import loadable from '@loadable/component'
const Moment = loadable.lib(() => import('moment'))
```
### lazy.lib
Create a loadable library "Suspense" ready.
| Arguments | Description |
| --------- | ---------------------------------------- |
| `loadFn` | The function call to load the component. |
```js
import { lazy } from '@loadable/component'
const Moment = lazy.lib(() => import('moment'))
```
### LoadableLibrary
A component created using `loadable.lib` or `lazy.lib`.
| Props | Description |
| ---------- | ---------------------------------------------------- |
| `children` | Function called when the library is loaded. |
| `ref` | Accepts a ref, populated when the library is loaded. |
| `fallback` | Fallback displayed during the loading. |
| `...` | Props are forwarded as first argument of `loadFn` |
## MIT
See [LICENSE](./LICENSE) for more information.

View File

@ -1,27 +1,27 @@
const path = require('path');
const LoadableWebpackPlugin = require('@loadable/webpack-plugin');
const LoadableBabelPlugin = require('@loadable/babel-plugin');
const babelPresetRazzle = require('razzle/babel');
const path = require('path')
const LoadableWebpackPlugin = require('@loadable/webpack-plugin')
const LoadableBabelPlugin = require('@loadable/babel-plugin')
const babelPresetRazzle = require('razzle/babel')
module.exports = {
modify: (config, { target }) => {
const appConfig = Object.assign({}, config);
modify: (config, { target }) => {
const appConfig = Object.assign({}, config)
if (target === 'web') {
const filename = path.resolve(__dirname, 'build/loadable-stats.json');
if (target === 'web') {
const filename = path.resolve(__dirname, 'build/loadable-stats.json')
appConfig.plugins = [
...appConfig.plugins,
new LoadableWebpackPlugin({ writeToDisk: true, filename })
];
}
appConfig.plugins = [
...appConfig.plugins,
new LoadableWebpackPlugin({ writeToDisk: true, filename }),
]
}
return appConfig;
},
return appConfig
},
modifyBabelOptions: () => ({
babelrc: false,
presets: [babelPresetRazzle],
plugins: [LoadableBabelPlugin]
})
};
modifyBabelOptions: () => ({
babelrc: false,
presets: [babelPresetRazzle],
plugins: [LoadableBabelPlugin],
}),
}

View File

@ -1,5 +1,5 @@
import React from 'react';
import React from 'react'
const About = () => (<div>About page</div>);
const About = () => <div>About page</div>
export default About;
export default About

View File

@ -1,12 +1,12 @@
import React from 'react';
import { Router, Link } from '@reach/router';
import loadable from '@loadable/component';
import './App.css';
import React from 'react'
import { Router, Link } from '@reach/router'
import loadable from '@loadable/component'
import './App.css'
const NotFound = loadable(() => import('./NotFound'));
const Home = loadable(() => import('./Home'));
const About = loadable(() => import('./About'));
const Contact = loadable(() => import('./Contact'));
const NotFound = loadable(() => import('./NotFound'))
const Home = loadable(() => import('./Home'))
const About = loadable(() => import('./About'))
const Contact = loadable(() => import('./Contact'))
const App = () => (
<React.Fragment>
@ -20,6 +20,6 @@ const App = () => (
<Contact path="/contact" fallback={<div>loading...</div>} />
</Router>
</React.Fragment>
);
)
export default App;
export default App

View File

@ -1,5 +1,5 @@
import React from 'react';
import React from 'react'
const Contact = () => (<div>Contact page</div>);
const Contact = () => <div>Contact page</div>
export default Contact;
export default Contact

View File

@ -1,11 +1,11 @@
/* eslint-disable react/jsx-one-expression-per-line */
import React from 'react';
import React from 'react'
const Intro = () => (
<p className="Home-intro">
To get started, edit <code>src/App.js</code> or <code>src/Home.js</code> and
save to reload.
</p>
);
)
export default Intro;
export default Intro

View File

@ -1,6 +1,6 @@
import React from 'react';
import logo from './react.svg';
import React from 'react'
import logo from './react.svg'
const Logo = () => <img src={logo} className="Home-logo" alt="logo" />;
const Logo = () => <img src={logo} className="Home-logo" alt="logo" />
export default Logo;
export default Logo

View File

@ -1,5 +1,5 @@
import React from 'react';
import React from 'react'
const Welcome = () => <h2>Welcome to Razzle</h2>;
const Welcome = () => <h2>Welcome to Razzle</h2>
export default Welcome;
export default Welcome

View File

@ -1,10 +1,10 @@
import React from 'react';
import loadable from '@loadable/component';
import './Home.css';
import React from 'react'
import loadable from '@loadable/component'
import './Home.css'
const Intro = loadable(() => import('./Intro'));
const Welcome = loadable(() => import('./Welcome'));
const Logo = loadable(() => import('./Logo'));
const Intro = loadable(() => import('./Intro'))
const Welcome = loadable(() => import('./Welcome'))
const Logo = loadable(() => import('./Logo'))
const Home = () => (
<div className="Home">
@ -25,6 +25,6 @@ const Home = () => (
</li>
</ul>
</div>
);
)
export default Home;
export default Home

View File

@ -1,5 +1,5 @@
import React from 'react';
import React from 'react'
const NotFound = () => (<div>Page could not be found</div>);
const NotFound = () => <div>Page could not be found</div>
export default NotFound;
export default NotFound

View File

@ -1,18 +1,14 @@
import React from 'react';
import { hydrate } from 'react-dom';
import { loadableReady } from '@loadable/component';
import App from './App';
import React from 'react'
import { hydrate } from 'react-dom'
import { loadableReady } from '@loadable/component'
import App from './App'
const root = document.getElementById('root');
const root = document.getElementById('root')
loadableReady(() => {
hydrate(
<App />,
root
);
});
hydrate(<App />, root)
})
if (module.hot) {
module.hot.accept();
module.hot.accept()
}

View File

@ -1,34 +1,34 @@
/* eslint-disable no-console, global-require */
import http from 'http';
import http from 'http'
let app = require('./server').default;
let app = require('./server').default
const server = http.createServer(app);
const server = http.createServer(app)
let currentApp = app;
let currentApp = app
server.listen(process.env.PORT || 3000, error => {
if (error) {
console.log(error);
}
if (error) {
console.log(error)
}
console.log('🚀 started');
});
console.log('🚀 started')
})
if (module.hot) {
console.log('✅ Server-side HMR Enabled!');
console.log('✅ Server-side HMR Enabled!')
module.hot.accept('./server', () => {
console.log('🔁 HMR Reloading `./server`...');
module.hot.accept('./server', () => {
console.log('🔁 HMR Reloading `./server`...')
try {
app = require('./server').default;
server.removeListener('request', currentApp);
server.on('request', app);
currentApp = app;
} catch (error) {
console.error(error);
}
});
try {
app = require('./server').default
server.removeListener('request', currentApp)
server.on('request', app)
currentApp = app
} catch (error) {
console.error(error)
}
})
}

View File

@ -1,28 +1,32 @@
import path from 'path';
import React from 'react';
import express from 'express';
import { html as htmlTemplate, oneLineTrim } from 'common-tags';
import { renderToString } from 'react-dom/server';
import { ServerLocation } from '@reach/router';
import { ChunkExtractor, ChunkExtractorManager } from '@loadable/server';
import App from './App';
import path from 'path'
import React from 'react'
import express from 'express'
import { html as htmlTemplate, oneLineTrim } from 'common-tags'
import { renderToString } from 'react-dom/server'
import { ServerLocation } from '@reach/router'
import { ChunkExtractor, ChunkExtractorManager } from '@loadable/server'
import App from './App'
const server = express();
const server = express()
server
.disable('x-powered-by')
.use(express.static(process.env.RAZZLE_PUBLIC_DIR))
.get('/*', (req, res) => {
const extractor = new ChunkExtractor({ statsFile: path.resolve('build/loadable-stats.json'), entrypoints: ['client'] });
const extractor = new ChunkExtractor({
statsFile: path.resolve('build/loadable-stats.json'),
entrypoints: ['client'],
})
const html = renderToString(
<ChunkExtractorManager extractor={extractor}>
<ServerLocation url={req.url}>
<App />
</ServerLocation>
</ChunkExtractorManager>
);
</ChunkExtractorManager>,
)
res.status(200).send(oneLineTrim(htmlTemplate`
res.status(200).send(
oneLineTrim(htmlTemplate`
<!doctype html>
<html lang="">
<head>
@ -38,7 +42,8 @@ server
${extractor.getScriptTags()}
</body>
</html>
`));
});
`),
)
})
export default server;
export default server

View File

@ -65,4 +65,5 @@ ${webExtractor.getStyleTags()}
}),
)
// eslint-disable-next-line no-console
app.listen(9000, () => console.log('Server started http://localhost:9000'))

11
netlify.toml Normal file
View File

@ -0,0 +1,11 @@
# Global settings applied to the whole site.
#
# “publish” is the directory to publish (relative to root of your repo),
# “command” is your build command,
# “base” is directory to change to before starting build. if you set base:
# that is where we will look for package.json/.nvmrc/etc not repo root!
[build]
base = "website"
publish = "website/public"
command = "yarn build:pp"

View File

@ -5,7 +5,7 @@ class LoadablePlugin {
constructor({ filename = 'loadable-stats.json', writeToDisk = false } = {}) {
this.opts = { filename, writeToDisk }
// The Webpack compiler instance
// The Webpack compiler instance
this.compiler = null
}

3
website/.eslintignore Normal file
View File

@ -0,0 +1,3 @@
.cache/
node_modules/
/public/

3
website/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.cache/
node_modules/
/public/

30
website/README.md Normal file
View File

@ -0,0 +1,30 @@
# Loadable Components website
[Documentation site](https://www.smooth-code.com/open-source/loadable-components/) for [loadable components](https://github.com/smooth-code/loadable-components). This website is running on [gatsbyjs](gatsbyjs.org).
## Getting Started
To install and run the docs site locally:
```bash
yarn
yarn dev
```
Then, open your favorite browser to [localhost:8000](http://localhost:8000/). GraphiQL runs at [localhost:8000/\_\_\_graphql](http://localhost:8000/___graphql).
## Contributing
Build the site to test locally.
```bash
yarn build
```
Serve the build.
```bash
yarn serve
```
Then, open your favorite browser to [localhost:9000](http://localhost:9000/) to verify everything looks correct.

10
website/gatsby-config.js Normal file
View File

@ -0,0 +1,10 @@
const { getGatsbyConfig } = require('smooth-doc/config')
module.exports = getGatsbyConfig({
root: __dirname,
name: 'Loadable Components',
slug: 'loadable-components',
github: 'https://github.com/smooth-code/loadable-components',
menu: ['Introduction', 'Guides', 'API'],
nav: [{ title: 'Docs', url: '/docs/' }],
})

13
website/gatsby-node.js Normal file
View File

@ -0,0 +1,13 @@
const { getGatsbyNode } = require('smooth-doc/node')
module.exports = getGatsbyNode({
root: __dirname,
})
module.exports.createPages = ({ actions }) => {
actions.createRedirect({
fromPath: `/docs/`,
toPath: `/docs/getting-started/`,
redirectInBrowser: true,
})
}

13
website/package.json Normal file
View File

@ -0,0 +1,13 @@
{
"private": true,
"scripts": {
"build": "gatsby build",
"build:pp": "gatsby build --prefix-paths",
"dev": "gatsby develop",
"serve": "gatsby serve"
},
"dependencies": {
"gatsby": "^2.0.63",
"smooth-doc": "^0.0.1-alpha.5"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
website/src/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,7 @@
---
title: Not Found
---
import { NotFound } from 'smooth-doc'
<NotFound />

View File

@ -0,0 +1,87 @@
---
menu: API
title: '@loadable/component'
order: 10
---
# @loadable/component
## Loadable
Create a loadable component.
| Arguments | Description |
| ------------------ | ---------------------------------------- |
| `loadFn` | The function call to load the component. |
| `options` | Optional options. |
| `options.fallback` | Fallback displayed during the loading. |
```js
import loadable from '@loadable/component'
const OtherComponent = loadable(() => import('./OtherComponent'))
```
## Lazy
Create a loadable component "Suspense" ready.
| Arguments | Description |
| --------- | ---------------------------------------- |
| `loadFn` | The function call to load the component. |
```js
import { lazy } from '@loadable/component'
const OtherComponent = lazy(() => import('./OtherComponent'))
```
## LoadableComponent
A component created using `loadable` or `lazy`.
| Props | Description |
| ---------- | ------------------------------------------------- |
| `fallback` | Fallback displayed during the loading. |
| `...` | Props are forwarded as first argument of `loadFn` |
## loadable.lib
Create a loadable library.
| Arguments | Description |
| ------------------ | ---------------------------------------- |
| `loadFn` | The function call to load the component. |
| `options` | Optional options. |
| `options.fallback` | Fallback displayed during the loading. |
```js
import loadable from '@loadable/component'
const Moment = loadable.lib(() => import('moment'))
```
## lazy.lib
Create a loadable library "Suspense" ready.
| Arguments | Description |
| --------- | ---------------------------------------- |
| `loadFn` | The function call to load the component. |
```js
import { lazy } from '@loadable/component'
const Moment = lazy.lib(() => import('moment'))
```
## LoadableLibrary
A component created using `loadable.lib` or `lazy.lib`.
| Props | Description |
| ---------- | ---------------------------------------------------- |
| `children` | Function called when the library is loaded. |
| `ref` | Accepts a ref, populated when the library is loaded. |
| `fallback` | Fallback displayed during the loading. |
| `...` | Props are forwarded as first argument of `loadFn` |

View File

@ -0,0 +1,124 @@
---
menu: API
title: '@loadable/server'
order: 20
---
# @loadable/server
## ChunkExtractor
Used to collect chunks server-side and get them as script tags or script elements.
| Arguments | Description |
| --------------------- | ----------------------------------------------------------- |
| `options` | An object options. |
| `options.statsFile` | Stats file path generated using `@loadable/webpack-plugin`. |
| `options.stats` | Stats generated using `@loadable/webpack-plugin`. |
| `options.entrypoints` | Webpack entrypoints to load (default to `["main"]`). |
| `options.outputPath` | Optional output path (only for `requireEntrypoint`). |
You must specify either `statsFile` or `stats` to be able to use `ChunkExtractor`.
Using `statsFile` will automatically reload stats for you if they change.
```js
import { ChunkExtractor } from '@loadable/server'
const statsFile = path.resolve('../dist/loadable-stats.json')
const chunkExtractor = new ChunkExtractor({ statsFile })
```
## chunkExtractor.collectChunks
Wrap your application in a `ChunkExtractorManager`.
| Arguments | Description |
| --------- | ------------------------------------------------------------ |
| `element` | JSX element that will be wrapped in `ChunkExtractorManager`. |
```js
const app = chunkExtractor.collectChunks(<YourApp />)
```
## chunkExtractor.requireEntrypoint
Require the entrypoint of your application as a commonjs module.
| Arguments | Description |
| --------- | ---------------------------------------------------------------- |
| `name` | Optional name the entrypoint, default to the first one (`main`). |
```js
const { default: App } = chunkExtractor.requireEntrypoint()
const app = <App />
```
## chunkExtractor.getScriptTags
Get scripts as a string of `<script>` tags.
```js
const body = `<body><div id="root">${html}</div>${chunkExtractor.getScriptTags()}</body>`
```
## chunkExtractor.getScriptElements
Get scripts as an array of React `<script>` elements.
```js
const body = renderToString(
<body>
<div id="root" dangerouslySetInnerHtml={{ __html: html }} />
{chunkExtractor.getScriptElements()}
</body>,
)
```
## chunkExtractor.getLinkTags
Get "prefetch" and "preload" links as a string of `<link>` tags.
```js
const head = `<head>${chunkExtractor.getLinkTags()}</head>`
```
## chunkExtractor.getLinkElements
Get "prefetch" and "preload" links as an array of React `<link>` elements.
```js
const head = renderToString(<head>{chunkExtractor.getLinkElements()}</head>)
```
## chunkExtractor.getStyleTags
Get style links as a string of `<link>` tags.
```js
const head = `<head>${chunkExtractor.getStyleTags()}</head>`
```
## chunkExtractor.getStyleElements
Get style links as an array of React `<link>` elements.
```js
const head = renderToString(<head>{chunkExtractor.getStyleElements()}</head>)
```
## ChunkExtractorManager
Used to inject a `ChunkExtractor` in the context of your application.
```js
import { ChunkExtractor, ChunkExtractorManager } from '@loadable/server'
const extractor = new ChunkExtractor()
const app = (
<ChunkExtractorManager extractor={extractor}>
<YourApp />
</ChunkExtractorManager>
)
```

View File

@ -0,0 +1,23 @@
---
menu: API
title: '@loadable/plugin'
order: 30
---
# @loadable/webpack-plugin
### LoadablePlugin
Create a webpack loadable plugin.
| Arguments | Description |
| --------------------- | ------------------------------------------------- |
| `options` | Optional options |
| `options.filename` | Stats filename (default to `loadable-stats.json`) |
| `options.writeToDisk` | Always write assets to disk (default to `false`) |
```js
new LoadablePlugin({ filename: 'stats.json', writeToDisk: true })
```
> Writing file to disk can be useful if you are using `razzle` or `webpack-dev-server`.

View File

@ -0,0 +1,66 @@
---
menu: Introduction
title: Code Splitting?
order: 10
---
# Code Splitting?
Code Splitting is an efficient way to reduce your bundle size: it speed up the loading of your application and reduce the payload size of your application.
Bundling is great, but as your app grows, your bundle will grow too. Especially if you are including large third-party libraries. You need to keep an eye on the code you are including in your bundle so that you dont accidentally make it so large that your app takes a long time to load.
To avoid winding up with a large bundle, its good to get ahead of the problem and start “splitting” your bundle. Code-Splitting is a feature supported by bundlers like Webpack and Browserify (via factor-bundle) which can create multiple bundles that can be dynamically loaded at runtime.
Code-splitting your app can help you “lazy-load” just the things that are currently needed by the user, which can dramatically improve the performance of your app. While you havent reduced the overall amount of code in your app, youve avoided loading code that the user may never need, and reduced the amount of code needed during the initial load.
## `import()`
The best way to introduce code-splitting into your app is through the dynamic `import()` syntax.
**Before:**
```js
import { add } from './math'
console.log(add(16, 26))
```
**After:**
```js
import('./math').then(math => {
console.log(math.add(16, 26))
})
```
> Note:
> The dynamic import() syntax is a ECMAScript (JavaScript) proposal not currently part of the language standard. It is expected to be accepted in the near future.
When Webpack comes across this syntax, it automatically starts code-splitting your app.
If youre setting up Webpack yourself, youll probably want to read [Webpacks guide on code splitting](https://webpack.js.org/guides/code-splitting/).
When using Babel, youll need to make sure that Babel can parse the dynamic import syntax but is not transforming it. For that you will need [@babel/plugin-syntax-dynamic-import](https://www.npmjs.com/package/@babel/plugin-syntax-dynamic-import).
## Code Splitting + React
React supports code splitting out of the box with [`React.lazy`](https://reactjs.org/docs/code-splitting.html#reactlazy). However it has [some limitations](/docs/loadable-vs-react-lazy), this is why `@loadable/component` exists.
In a React application, most of the time you want to split your components. Splitting a component implies to be able to wait for this component to be loaded (showing a fallack during loading) but also to handle errors.
**Example of component splitting:**
```js
import loadable from '@loadable/component'
const OtherComponent = loadable(() => import('./OtherComponent'))
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
)
}
```

View File

@ -0,0 +1,23 @@
---
menu: Guides
title: Component Splitting
order: 5
---
# Component Splitting
`loadable` lets you easily import components and reuse it in your code.
```js
import loadable from '@loadable/component'
const OtherComponent = loadable(() => import('./OtherComponent'))
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
)
}
```

View File

@ -0,0 +1,19 @@
---
menu: Guides
title: Delay
order: 50
---
# Delay
To avoid flashing a loader if the loading is very fast, you could implement a minimum delay. There is no built-in API in `@loadable/component` but you could do it using [`p-min-delay`](https://github.com/sindresorhus/p-min-delay).
```js
import loadable from '@loadable/component'
import pMinDelay from 'p-min-delay'
// Wait a minimum of 200ms before loading home.
export const OtherComponent = loadable(() =>
pMinDelay(import('./OtherComponent'), 200)
)
```

View File

@ -0,0 +1,24 @@
---
menu: Guides
title: Full dynamic import
order: 20
---
# Full dynamic import
Webpack accepts [full dynamic imports](https://webpack.js.org/api/module-methods/#import-), you can use them to create a reusable Loadable Component.
```js
import loadable from '@loadable/component'
const AsyncPage = loadable(props => import(`./${props.page}`))
function MyComponent() {
return (
<div>
<AsyncPage page="Home" />
<AsyncPage page="Contact" />
</div>
)
}
```

View File

@ -0,0 +1,26 @@
---
menu: Guides
title: Error boundaries
order: 45
---
# Error Boundaries
If the other module fails to load (for example, due to network failure), it will trigger an error. You can handle these errors to show a nice user experience and manage recovery with [Error Boundaries](https://reactjs.org/docs/error-boundaries.html). Once youve created your Error Boundary, you can use it anywhere above your lazy components to display an error state when theres a network error.
```js
import MyErrorBoundary from '/MyErrorBoundary'
const OtherComponent = loadable(() => import('./OtherComponent'))
const AnotherComponent = loadable(() => import('./AnotherComponent'))
const MyComponent = () => (
<div>
<MyErrorBoundary>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</MyErrorBoundary>
</div>
)
```

View File

@ -0,0 +1,37 @@
---
menu: Guides
title: Fallback without Suspense
order: 40
---
# Fallback without Suspense
You can specify a `fallback` in `loadable` options.
```js
const OtherComponent = loadable(() => import('./OtherComponent'), {
fallback: <div>Loading...</div>,
})
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
)
}
```
You can also specify a `fallback` in props:
```js
const OtherComponent = loadable(() => import('./OtherComponent'))
function MyComponent() {
return (
<div>
<OtherComponent fallback={<div>Loading...</div>} />
</div>
)
}
```

View File

@ -0,0 +1,41 @@
---
menu: Introduction
title: Getting Started
order: 5
---
# Getting Started
Follow these few steps to get ready with `@loadable/component`.
## Installation
Installing `@loadable/component` only takes a single command and you're ready to roll:
```bash
npm install @loadable/component
# or use yarn
yarn add @loadable/component
```
> `@loadable/server`, `@loadable/webpack-plugin` and `@loadable/babel-plugin` are only required for [Server Side Rendering](/docs/server-side-rendering).
## Split your first component
Loadable lets you render a dynamic import as a regular component.
```js
import loadable from '@loadable/component'
const OtherComponent = loadable(() => import('./OtherComponent'))
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
)
}
```
That's it, `OtherComponent` will now be loaded in a separated bundle!

View File

@ -0,0 +1,54 @@
---
menu: Guides
title: Loading library
order: 10
---
# Library Splitting
`loadable.lib` lets you defer the loading of a library. It takes a render props called when the library is loaded.
```js
import loadable from '@loadable/component'
const Moment = loadable.lib(() => import('moment'))
function FromNow({ date }) {
return (
<div>
<Moment fallback={date.toLocaleDateString()}>
{({ default: moment }) => moment(date).fromNow()}
</Moment>
</div>
)
}
```
You can also use a `ref`, populated when the library is loaded.
```js
import loadable from '@loadable/component'
const Moment = loadable.lib(() => import('moment'))
class MyComponent {
moment = React.createRef()
handleClick = () => {
if (this.moment.current) {
return alert(this.moment.current.default.format('HH:mm'))
}
}
render() {
return (
<div>
<button onClick={this.handleClick}>What time is it?</button>
<Moment ref={this.moment} />
</div>
)
}
}
```
> You can also pass a function to `ref`, called when the library is loaded.

View File

@ -0,0 +1,70 @@
---
menu: Introduction
title: Comparison with React.lazy
order: 20
---
# Comparison with React.lazy
What are the differences between `React.lazy` and `@loadable/components`?
[`React.lazy`](https://reactjs.org/docs/code-splitting.html#reactlazy) is the recommended solution for Code Splitting. It uses Suspense and it is maintained by React.
If you are already using `React.lazy` and if you are good with it, you don't need `@loadable/component`.
If you feel limited or if you need SSR, then `@loadable/component` is the solution.
## Comparison table
| Library | Suspense | SSR | Library splitting | `` import(`./${value}`) `` |
| --------------------- | -------- | --- | ----------------- | -------------------------- |
| `React.lazy` | ✅ | ❌ | ❌ | ❌ |
| `@loadable/component` | ✅ | ✅ | ✅ | ✅ |
## Suspense
Suspense is supported by `React.lazy` and by `@loadable/component`. `@loadable/component` can also be used without Suspense.
## Server Side Rendering
Suspense is not available server-side and `React.lazy` can only works with Suspense. That's why today, `React.lazy` is not an option if you need Server Side Rendering.
`@loadable/component` provides a complete solution to make [Server Side Rendering](/docs/server-side-rendering) possible.
## Library splitting
`@loadable/component` supports library splitting using render props. This is not possible with `React.lazy`.
## Full dynamic import
Full dynamic import also called agressive code splitting is a feature supported by Webpack. It consists of passing a dynamic value to the dynamic `import()` function.
```js
// All files that could match this pattern will be automatically code splitted.
const loadFile = file => import(`./${file}`)
```
In React, it permits to create reusable components:
```js
import loadable from '@loadable/component'
const AsyncPage = loadable(props => import(`./${props.page}`))
function MyComponent() {
return (
<div>
<AsyncPage page="Home" />
<AsyncPage page="Contact" />
</div>
)
}
```
This feature is not supported by `React.lazy`.
## Note about `react-loadable`
[react-loadable](https://github.com/jamiebuilds/react-loadable) was the recommended way for React code splitting for a long time. However, today it is not maintained any more and it is not compatible with Webpack v4+ and Babel v7+.
If you use it, it is recommended to migrate to `React.lazy` or `@loadable/component`.

View File

@ -0,0 +1,21 @@
---
menu: Guides
title: Prefetching
order: 60
---
# Prefetching
Loadable Components is fully compatible with [webpack hints `webpackPrefetch` and `webpackPreload`](https://webpack.js.org/guides/code-splitting/#prefetching-preloading-modules).
Most of the time, you want to "prefetch" a component, it means it will be loaded when the browser is idle. You can do it by adding `/* webpackPrefetch: true */` inside your import statement.
```js
import loadable from '@loadable/component'
const OtherComponent = loadable(() =>
import(/* webpackPrefetch: true */ './OtherComponent')
)
```
> You can extract prefetched resources server-side to add `<link rel="prefetch">` in your head.

View File

@ -0,0 +1,190 @@
---
menu: Guides
title: Server Side Rendering
order: 65
---
# Server Side Rendering
## Install
```bash
npm install @loadable/server && npm --save-dev @loadable/babel-plugin @loadable/webpack-plugin
# or using yarn
yarn add @loadable/server && yarn --dev @loadable/babel-plugin @loadable/webpack-plugin
```
## Guide
### 1. Install `@loadable/babel-plugin`
**.babelrc**
```json
{
"plugins": ["@loadable/babel-plugin"]
}
```
### 2. Install `@loadable/webpack-plugin`
**webpack.config.js**
```js
const LoadablePlugin = require('@loadable/webpack-plugin')
module.exports = {
// ...
plugins: [new LoadablePlugin()],
}
```
### 3. Setup `ChunkExtractor` server-side
```js
import { ChunkExtractor } from '@loadable/server'
// This is the stats file generated by webpack loadable plugin
const statsFile = path.resolve('../dist/loadable-stats.json')
// We create an extractor from the statsFile
const extractor = new ChunkExtractor({ statsFile })
// Wrap your application using "collectChunks"
const jsx = extractor.collectChunks(<YourApp />)
// Render your application
const html = renderToString(jsx)
// You can now collect your script tags
const scriptTags = extractor.getScriptTags() // or extractor.getScriptElements();
// You can also collect your "preload/prefetch" links
const scriptTags = extractor.getLinkTags() // or extractor.getLinkElements();
// And you can even collect your style tags (if you use "mini-css-extract-plugin")
const styleTags = extractor.getStyleTags() // or extractor.getStyleElements();
```
### 4. Add `loadableReady` client-side
Loadable components loads all your scripts asynchronously to ensure optimal performances. All scripts are loaded in parallel, so you have to wait for them to be ready using `loadableReady`.
```js
import { loadableReady } from '@loadable/component'
loadableReady(() => {
const root = document.getElementById('main')
hydrate(<App />, root)
})
```
**🚀 [Checkout the complete example in this repository](https://github.com/smooth-code/loadable-components/tree/master/examples/server-side-rendering)**
## Collecting chunks
The basic API goes as follows:
```js
import { renderToString } from 'react-dom/server'
import { ChunkExtractor } from '@loadable/server'
const statsFile = path.resolve('../dist/loadable-stats.json')
const extractor = new ChunkExtractor({ statsFile })
const html = renderToString(extractor.collectChunks(<YourApp />))
const scriptTags = extractor.getScriptTags() // or extractor.getScriptElements();
```
The `collectChunks` method wraps your element in a provider. Optionally you can use the `ChunkExtractorManager` provider directly, instead of this method. Just make sure not to use it on the client-side.
```js
import { renderToString } from 'react-dom/server'
import { ChunkExtractor, ChunkExtractorManager } from '@loadable/server'
const statsFile = path.resolve('../dist/loadable-stats.json')
const extractor = new ChunkExtractor({ statsFile })
const html = renderToString(
<ChunkExtractorManager extractor={extractor}>
<YourApp />
</ChunkExtractorManager>,
)
const scriptTags = extractor.getScriptTags() // or extractor.getScriptElements();
```
The `extractor.getScriptTags()` returns a string of multiple `<script>` tags marked as "async". You have to wait for them to be ready using `loadableReady`.
Alternatively the `ChunkExtractor` also has a `getScriptElements()` method that returns an array of React elements.
## Streaming rendering
Loadable is compatible with streaming rendering, if you use it you have to include script when the stream is complete.
```js
import { renderToNodeStream } from 'react-dom/server'
import { ChunkExtractor } from '@loadable/server'
// if you're using express.js, you'd have access to the response object "res"
// typically you'd want to write some preliminary HTML, since React doesn't handle this
res.write('<html><head><title>Test</title></head><body>')
const statsFile = path.resolve('../dist/loadable-stats.json')
const chunkExtractor = new ChunkExtractor({ statsFile })
const jsx = chunkExtractor.collectChunks(<YourApp />)
const stream = renderToNodeStream(jsx)
// you'd then pipe the stream into the response object until it's done
stream.pipe(
res,
{ end: false },
)
// and finalize the response with closing HTML
stream.on('end', () =>
res.end(`${chunkExtractor.getScriptTags()}</body></html>`),
)
```
> Streaming rendering is not compatible with prefetch `<link>` tags.
## Prefetching
[Webpack prefetching](https://webpack.js.org/guides/code-splitting/#prefetching-preloading-modules) is supported out of the box by Loadable. [`<link rel="preload">` and `<link rel="prefetch">`](https://css-tricks.com/prefetching-preloading-prebrowsing/) can be added directly server-side to improve performances.
```js
import path from 'path'
import { ChunkExtractor, ChunkExtractorManager } from '@loadable/server'
const statsFile = path.resolve('../dist/loadable-stats.json')
const extractor = new ChunkExtractor({ statsFile })
const jsx = extractor.collectChunks(<YourApp />)
const html = renderToString(jsx)
const linkTags = extractor.getLinkTags() // or chunkExtractor.getLinkElements();
const html = `<html>
<head>${linkTags}</head>
<body>
<div id="root">${html}</div>
</body>
</html>`
```
> It only works with `renderToString` API. Since `<link>` must be added in the `<head>`, you can't do it using `renderToNodeStream`.
## CSS
Extracted CSS using plugins like ["mini-css-extract-plugin"](https://github.com/webpack-contrib/mini-css-extract-plugin) are automatically collected, you can get them using `getStyleTags` or `getStyleElements`.
```js
import { renderToString } from 'react-dom/server'
import { ChunkExtractor } from '@loadable/server'
const statsFile = path.resolve('../dist/loadable-stats.json')
const extractor = new ChunkExtractor({ statsFile })
const html = renderToString(extractor.collectChunks(<YourApp />))
const styleTags = extractor.getStyleTags() // or extractor.getStyleElements();
```

View File

@ -0,0 +1,29 @@
---
menu: Guides
title: Suspense
order: 30
---
# Suspense
`@loadable/component` exposes a `lazy` method that acts similarly as `React.lazy` one.
```js
import { lazy } from '@loadable/component'
const OtherComponent = lazy(() => import('./OtherComponent'))
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
)
}
```
> Use `lazy.lib` for libraries.
> ⚠️ Suspense is not yet available for server-side rendering.

View File

@ -0,0 +1,19 @@
---
menu: Guides
title: Timeout
order: 55
---
# Timeout
Infinite loading is not good for user experience, to avoid it implementing a timeout is a good workaround. You can do it using a third party module like [`promise-timeout`](https://github.com/building5/promise-timeout):
```js
import loadable from '@loadable/component'
import { timeout } from 'promise-timeout'
// Wait a maximum of 2s before sending an error.
export const OtherComponent = loadable(() =>
timeout(import('./OtherComponent'), 2000)
)
```

View File

@ -0,0 +1,48 @@
---
title: Loadable Components
---
import { Grid, Row, Col } from '@smooth-ui/core-sc'
import { HomeHero, ShowCase } from 'smooth-doc'
import Helmet from 'react-helmet'
<Helmet>
<title>Loadable Components - React code splitting</title>
</Helmet>
<HomeHero title="React code splitting made easy." />
<ShowCase>
<Grid maxWidth={660} gutter={20}>
<Row>
<Col xs={12} md>
<h2>What is it?</h2>
<ul>
<li>A React code splitting library</li>
<li>
Not an alternative to <code>React.lazy</code>
</li>
<li>
A solution{' '}
<a
href="https://reactjs.org/docs/code-splitting.html#reactlazy"
target="_blank"
rel="noopener"
>
recommended by React Team
</a>
</li>
</ul>
</Col>
<Col xs={12} md="auto">
<h2>Features</h2>
<ul>
<li>📚 Library splitting</li>
<li>⚡️ Prefetching</li>
<li>💫 Server Side Rendering</li>
<li>🎛 Full dynamic import</li>
</ul>
</Col>
</Row>
</Grid>
</ShowCase>

13697
website/yarn.lock Normal file

File diff suppressed because it is too large Load Diff