mirror of
https://github.com/gregberge/loadable-components.git
synced 2026-01-25 15:24:15 +00:00
docs: add website
This commit is contained in:
parent
26dd34478e
commit
428599cdc7
@ -3,3 +3,5 @@ node_modules/
|
||||
dist/
|
||||
lib/
|
||||
build/
|
||||
/website/.cache/
|
||||
/website/public/
|
||||
2
.github/ISSUE_TEMPLATE/feature.md
vendored
2
.github/ISSUE_TEMPLATE/feature.md
vendored
@ -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?
|
||||
|
||||
@ -4,4 +4,6 @@ node_modules/
|
||||
__fixtures__/
|
||||
CHANGELOG.md
|
||||
package.json
|
||||
lerna.json
|
||||
lerna.json
|
||||
/website/.cache/
|
||||
/website/public/
|
||||
@ -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
305
README.md
@ -12,32 +12,21 @@
|
||||
[](https://david-dm.org/smooth-code/loadable-components?type=dev)
|
||||
[](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 you’ve created your Error Boundary, you can use it anywhere above your lazy components to display an error state when there’s 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.
|
||||
|
||||
@ -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],
|
||||
}),
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
11
netlify.toml
Normal 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"
|
||||
@ -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
3
website/.eslintignore
Normal file
@ -0,0 +1,3 @@
|
||||
.cache/
|
||||
node_modules/
|
||||
/public/
|
||||
3
website/.gitignore
vendored
Normal file
3
website/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
.cache/
|
||||
node_modules/
|
||||
/public/
|
||||
30
website/README.md
Normal file
30
website/README.md
Normal 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
10
website/gatsby-config.js
Normal 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
13
website/gatsby-node.js
Normal 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
13
website/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
BIN
website/src/images/home-logo.png
Normal file
BIN
website/src/images/home-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
website/src/images/logo.png
Normal file
BIN
website/src/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
7
website/src/pages/404.mdx
Normal file
7
website/src/pages/404.mdx
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Not Found
|
||||
---
|
||||
|
||||
import { NotFound } from 'smooth-doc'
|
||||
|
||||
<NotFound />
|
||||
87
website/src/pages/docs/api-loadable-component.mdx
Normal file
87
website/src/pages/docs/api-loadable-component.mdx
Normal 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` |
|
||||
124
website/src/pages/docs/api-loadable-server.mdx
Normal file
124
website/src/pages/docs/api-loadable-server.mdx
Normal 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>
|
||||
)
|
||||
```
|
||||
23
website/src/pages/docs/api-loadable-webpack-plugin.mdx
Normal file
23
website/src/pages/docs/api-loadable-webpack-plugin.mdx
Normal 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`.
|
||||
66
website/src/pages/docs/code-splitting.mdx
Normal file
66
website/src/pages/docs/code-splitting.mdx
Normal 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 don’t accidentally make it so large that your app takes a long time to load.
|
||||
|
||||
To avoid winding up with a large bundle, it’s 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 haven’t reduced the overall amount of code in your app, you’ve 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 you’re setting up Webpack yourself, you’ll probably want to read [Webpack’s guide on code splitting](https://webpack.js.org/guides/code-splitting/).
|
||||
|
||||
When using Babel, you’ll 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>
|
||||
)
|
||||
}
|
||||
```
|
||||
23
website/src/pages/docs/component-splitting.mdx
Normal file
23
website/src/pages/docs/component-splitting.mdx
Normal 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>
|
||||
)
|
||||
}
|
||||
```
|
||||
19
website/src/pages/docs/delay.mdx
Normal file
19
website/src/pages/docs/delay.mdx
Normal 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)
|
||||
)
|
||||
```
|
||||
24
website/src/pages/docs/dynamic-import.mdx
Normal file
24
website/src/pages/docs/dynamic-import.mdx
Normal 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>
|
||||
)
|
||||
}
|
||||
```
|
||||
26
website/src/pages/docs/error-boundaries.mdx
Normal file
26
website/src/pages/docs/error-boundaries.mdx
Normal 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 you’ve created your Error Boundary, you can use it anywhere above your lazy components to display an error state when there’s 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>
|
||||
)
|
||||
```
|
||||
37
website/src/pages/docs/fallback.mdx
Normal file
37
website/src/pages/docs/fallback.mdx
Normal 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>
|
||||
)
|
||||
}
|
||||
```
|
||||
41
website/src/pages/docs/getting-started.mdx
Normal file
41
website/src/pages/docs/getting-started.mdx
Normal 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!
|
||||
54
website/src/pages/docs/library-splitting.mdx
Normal file
54
website/src/pages/docs/library-splitting.mdx
Normal 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.
|
||||
70
website/src/pages/docs/loadable-vs-react-lazy.mdx
Normal file
70
website/src/pages/docs/loadable-vs-react-lazy.mdx
Normal 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`.
|
||||
21
website/src/pages/docs/prefetching.mdx
Normal file
21
website/src/pages/docs/prefetching.mdx
Normal 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.
|
||||
190
website/src/pages/docs/server-side-rendering.mdx
Normal file
190
website/src/pages/docs/server-side-rendering.mdx
Normal 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();
|
||||
```
|
||||
29
website/src/pages/docs/suspense.mdx
Normal file
29
website/src/pages/docs/suspense.mdx
Normal 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.
|
||||
19
website/src/pages/docs/timeout.mdx
Normal file
19
website/src/pages/docs/timeout.mdx
Normal 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)
|
||||
)
|
||||
```
|
||||
48
website/src/pages/index.mdx
Normal file
48
website/src/pages/index.mdx
Normal 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
13697
website/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user