From 99249633afd78b83a8abaf93f6292f4530ba786c Mon Sep 17 00:00:00 2001 From: riley napier Date: Tue, 5 Dec 2017 10:21:16 -0800 Subject: [PATCH] docs(Fixing formatting for Readme): --- README.md | 223 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 127 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index 4dc68e3..71c63d3 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,100 @@ -[![Reactlandia Chat](https://s3-us-west-1.amazonaws.com/cdn.reactlandia.com/reactlandia-chat.png)](https://gitter.im/Reactlandia/Lobby) + + Reactlandia Chat + -[![Edit React Universal Component on StackBlitz](https://raw.githubusercontent.com/gothinkster/realworld/master/media/edit_on_blitz.png)](https://stackblitz.com/edit/react-pnzphy) + + Edit React Universal Component on StackBlitz + + + + Edit React Universal Component on CodeSandBox + -[![Edit React Universal Component on CodeSandBox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/faceyspacey/redux-first-router-codesandbox/tree/master/?module=r1oVP5YEUZ) # React Universal Component -[![Version](https://img.shields.io/npm/v/react-universal-component.svg)](https://www.npmjs.com/package/react-universal-component) [![Build Status](https://travis-ci.org/faceyspacey/react-universal-component.svg?branch=master)](https://travis-ci.org/faceyspacey/react-universal-component) [![Coverage Status](https://lima.codeclimate.com/github/faceyspacey/react-universal-component/badges/coverage.svg)](https://lima.codeclimate.com/github/faceyspacey/react-universal-component/coverage) [![Green Keeper](https://badges.greenkeeper.io/faceyspacey/react-universal-component.svg)](https://greenkeeper.io) [![GPA](https://lima.codeclimate.com/github/faceyspacey/react-universal-component/badges/gpa.svg)](https://lima.codeclimate.com/github/faceyspacey/react-universal-component) [![Downloads](https://img.shields.io/npm/dt/react-universal-component.svg)](https://www.npmjs.com/package/react-universal-component) [![License](https://img.shields.io/npm/l/react-universal-component.svg)](https://www.npmjs.com/package/react-universal-component) +

+ + Version + + + + Build Status + + + + Coverage Status + + + + Green Keeper + + + + GPA + -![](https://cdn.reactlandia.com/universal-component-banner.png) + + Downloads + -🍾🍾🍾 [GIT CLONE 2.0 LOCAL DEMO](https://github.com/faceyspacey/universal-demo) πŸš€πŸš€πŸš€ + + License + +

+ + +

+ +

+ +

+🍾🍾🍾 GIT CLONE 2.0 LOCAL DEMO πŸš€πŸš€πŸš€ +

For "power users" the SPA is dead. If you're not universally rendering on the server you're doing it "wrong." You're losing money for you, your clients, your employers. All hail the Google god. -The real problem has been **simultaneous SSR + Splitting**. If you've ever attempted such, _you know_. This is a one-of-a-kind solution that brings it all together. +The real problem has been **simultaneous SSR + Splitting**. If you've ever attempted such, *you know*. This is a one-of-a-kind solution that brings it all together. -_This is the final universal component for React you'll ever need, and it looks like this:_ +*This is the final universal component for React you'll ever need, and it looks like this:* -```javascript +```js import universal from 'react-universal-component' const UniversalComponent = universal(props => import(`./${props.page}`)) -export default () => ( +export default () =>
- +
-) ``` -It's made possible by our [PR to webpack](https://github.com/webpack/webpack/pull/5235) which built support for ``require.resolveWeak(`'./${page}`)``. Before it couldn't be dynamic--i.e. it supported one module, not a folder of modules. +It's made possible by our [PR to webpack](https://github.com/webpack/webpack/pull/5235) which built support for ```require.resolveWeak(`'./${page}`)```. Before it couldn't be dynamic--i.e. it supported one module, not a folder of modules. You no longer need to create a hash of all your universal or loadable components. You can frictionlessly support multiple components in one HoC as if imports weren't static. This seamingly small thing--we predict--will lead to universal rendering finally becoming commonplace. It's what a universal component for React is supposed to be. -Of course, you also need [webpack-flush-chunks](https://github.com/faceyspacey/webpack-flush-chunks) to bring this together server-side. Ultimately that's the real foundation here and the most challenging part. Packages in the past like _React Loadable_ did not address this aspect. They excelled at the SPA. In terms of universal rendering, they got you maybe 15% of the way by providing the module IDs rendered. There's a lot more than that. +Of course, you also need [webpack-flush-chunks](https://github.com/faceyspacey/webpack-flush-chunks) to bring this together server-side. Ultimately that's the real foundation here and the most challenging part. Packages in the past like *React Loadable* did not address this aspect. They excelled at the SPA. In terms of universal rendering, they got you maybe 15% of the way by providing the module IDs rendered. There's a lot more than that. -**Webpack Flush Chunks** ensures you serve all the chunks rendered on the server to the client in style. To be clear, it's been impossible until now. This is the first general solution to do it, and still the only one. You _must_ use it in combination with React Universal Component to fulfill the universal code splitting dream. +**Webpack Flush Chunks** ensures you serve all the chunks rendered on the server to the client in style. To be clear, it's been impossible until now. This is the first general solution to do it, and still the only one. You *must* use it in combination with React Universal Component to fulfill the universal code splitting dream. -In the future both packages will be distilled into one product called `universal-render`--or **_"Universal"_** for short. The transition will be seamless. We're making this space as easy as possible for "power users" like yourself that prefer the _frameworkless_ approach over the constraints of a framework like Next.js. +In the future both packages will be distilled into one product called `universal-render`--or ***"Universal"*** for short. The transition will be seamless. We're making this space as easy as possible for "power users" like yourself that prefer the *frameworkless* approach over the constraints of a framework like Next.js. -> DEFINITION: "Universal Rendering" is _simutlaneous_ SSR + Splitting, not trading one for the other. +> DEFINITION: "Universal Rendering" is *simutlaneous* SSR + Splitting, not trading one for the other. ## I didn't know Universal Rendering was such a pain... - -That's probably because you were trapped in SPA land. If you didn't know how much of a pain in the ass _universal rendering_ has been, check this quote from the React Router docs: +That's probably because you were trapped in SPA land. If you didn't know how much of a pain in the ass *universal rendering* has been, check this quote from the React Router docs: ![require-universal-component react-router quote](./react-router-quote.png) > If you were already in the know, you're probably one of our first users, and we thank you for your support and feeling the essence of our mission. Thank god this is over! -## Installation -`yarn add react-universal-component` +## Installation -_.babelrc:_ +```yarn add react-universal-component``` -```javascript +*.babelrc:* +```js { "plugins": ["universal-import"] } @@ -64,6 +102,7 @@ _.babelrc:_ > For Typescript or environments without Babel, just copy what [babel-plugin-universal-import](https://github.com/faceyspacey/babel-plugin-universal-import) does. + **Reactlandia Articles:** - **[code-cracked-for-ssr-plus-splitting-in-reactlandia](https://medium.com/@faceyspacey/code-cracked-for-code-splitting-ssr-in-reactlandia-react-loadable-webpack-flush-chunks-and-1a6b0112a8b8)** πŸš€ @@ -76,81 +115,78 @@ _.babelrc:_ ## Other Packages You Will Need/Want -To be clear, you can get started with just the simple `HoC` shown at the top of the page, but to accomplish universal rendering, you will need to follow the directions in the _webpack-flush-chunks_ package: +To be clear, you can get started with just the simple `HoC` shown at the top of the page, but to accomplish universal rendering, you will need to follow the directions in the *webpack-flush-chunks* package: - **[webpack-flush-chunks](https://github.com/faceyspacey/webpack-flush-chunks)** -And if you want CSS chunks _(which we highly recommend)_, you will need: - +And if you want CSS chunks *(which we highly recommend)*, you will need: - [extract-css-chunks-webpack-plugin](https://github.com/faceyspacey/extract-css-chunks-webpack-plugin) + ## API + Options -```javascript + +```js universal(asyncComponent, options) ``` **asyncComponent:** - -- ``props => import(`./${props.page}`)`` -- `import('./Foo')` _// doesn't need to be wrapped in a function when using the babel plugin!_ +- ```props => import(`./${props.page}`)``` +- `import('./Foo')` *// doesn't need to be wrapped in a function when using the babel plugin!* - `(props, cb) => require.ensure([], require => cb(null, require('./Foo')))` The first argument can be a function that returns a promise, a promise itself, or a function that takes a node-style callback. The most powerful and popular is a function that takes props as an argument. **Options (all are optional):** -- `loading`: LoadingComponent, -- _default: a simple one is provided for you_ -- `error`: ErrorComponent, -- _default: a simple one is provided for you_ -- `key`: `'foo'` || `module => module.foo` -- _default: `default` export in ES6 and `module.exports` in ES5_ -- `timeout`: `15000` -- _default_ +- `loading`: LoadingComponent, -- *default: a simple one is provided for you* +- `error`: ErrorComponent, -- *default: a simple one is provided for you* +- `key`: `'foo'` || `module => module.foo` -- *default: `default` export in ES6 and `module.exports` in ES5* +- `timeout`: `15000` -- *default* - `onError`: `(error, { isServer }) => handleError(error, isServer) - `onLoad`: `(module, { isSync, isServer }, props, context) => do(module, isSync, isServer, props, context)` -- `minDelay`: `0` -- _default_ -- `alwaysDelay`: `false` -- _default_ -- `loadingTransition`: `true` -- _default_ +- `minDelay`: `0` -- *default* +- `alwaysDelay`: `false` -- *default* +- `loadingTransition`: `true` -- *default* -**In Depth:** +**In Depth:** > All components can be classes/functions or elements (e.g: `Loading` or ``) - `loading` is the component class or function corresponding to your stateless component that displays while the primary import is loading. While testing out this package, you can leave it out as a simple default one is used. - `error` similarly is the component that displays if there are any errors that occur during your aynschronous import. While testing out this package, you can leave it out as a simple default one is used. -- `key` lets you specify the export from the module you want to be your component if it's not `default` in ES6 or `module.exports` in ES5\. It can be a string corresponding to the export key, or a function that's passed the entire module and returns the export that will become the component. +- `key` lets you specify the export from the module you want to be your component if it's not `default` in ES6 or `module.exports` in ES5. It can be a string corresponding to the export key, or a function that's passed the entire module and returns the export that will become the component. - `timeout` allows you to specify a maximum amount of time before the `error` component is displayed. The default is 15 seconds. -- `onError` is a callback called if async imports fail. It does not apply to sync requires. -- `onLoad` is a callback function that receives the _entire_ module. It allows you to export and put to use things other than your `default` component export, like reducers, sagas, etc. E.g: +- `onError` is a callback called if async imports fail. It does not apply to sync requires. -```javascript +- `onLoad` is a callback function that receives the *entire* module. It allows you to export and put to use things other than your `default` component export, like reducers, sagas, etc. E.g: +```js onLoad: (module, info, props, context) => { context.store.replaceReducer({ ...otherReducers, foo: module.fooReducer }) // if a route triggered component change, new reducers needs to reflect it - context.store.dispatch({ - type: 'INIT_ACTION_FOR_ROUTE', - payload: { param: props.param } - }) + context.store.dispatch({ type: 'INIT_ACTION_FOR_ROUTE', payload: { param: props.param } }) } -``` - -- `onLoad` _(continued)_ - **As you can see we have thought of everything you might need to really do code-splitting right (we have real apps that use this stuff).** `onLoad` is fired directly before the component is rendered so you can setup any reducers/etc it depends on. Unlike the `onAfter` prop, this _option_ to the `universal` _HOC_ is only fired the first time the module is received. _Also note_: it will fire on the server, so do `if (!isServer)` if you have to. But also keep in mind you will need to do things like replace reducers on both the server + client for the imported component that uses new reducers to render identically in both places. +```` +- `onLoad` *(continued)* - **As you can see we have thought of everything you might need to really do code-splitting right (we have real apps that use this stuff).** `onLoad` is fired directly before the component is rendered so you can setup any reducers/etc it depends on. Unlike the `onAfter` prop, this *option* to the `universal` *HOC* is only fired the first time the module is received. *Also note*: it will fire on the server, so do `if (!isServer)` if you have to. But also keep in mind you will need to do things like replace reducers on both the server + client for the imported component that uses new reducers to render identically in both places. - `minDelay` is essentially the minimum amount of time the `loading` component will always show for. It's good for enforcing silky smooth animations, such as during a 500ms sliding transition. It insures the re-render won't happen until the animation is complete. It's often a good idea to set this to something like 300ms even if you don't have a transition, just so the loading spinner shows for an appropriate amount of time without jank. -- `alwaysDelay` is a boolean you can set to true (_default: false_) to guarantee the `minDelay` is always used (i.e. even when components cached from previous imports and therefore synchronously and instantly required). This can be useful for guaranteeing animations operate as you want without having to wire up other components to perform the task. _Note: this only applies to the client when your `UniversalComponent` uses dynamic expressions to switch between multiple components._ +- `alwaysDelay` is a boolean you can set to true (*default: false*) to guarantee the `minDelay` is always used (i.e. even when components cached from previous imports and therefore synchronously and instantly required). This can be useful for guaranteeing animations operate as you want without having to wire up other components to perform the task. *Note: this only applies to the client when your `UniversalComponent` uses dynamic expressions to switch between multiple components.* - `loadingTransition` when set to `false` allows you to keep showing the current component when the `loading` component would otherwise show during transitions from one component to the next. + ## Flushing for SSR Below is the most important thing on this page. It's a quick example of the connection between this package and [webpack-flush-chunks](https://github.com/faceyspacey/webpack-flush-chunks): -```javascript +```js import { clearChunks, flushChunkNames } from 'react-universal-component/server' import flushChunks from 'webpack-flush-chunks' import ReactDOM from 'react-dom/server' @@ -181,7 +217,7 @@ export default function serverRender(req, res) => { If you build step and your render step are separate (i.e. using a static site generator like `react-static`) we can use a Provider type component to locate the components that should be included on the client. This is not the recommended use of locating chunk names and only should be used when absolutely necessary. It uses React's context functionality to pass the `report` function to react-universal-component. -```javascript +```js import { ReportChunks } from 'react-universal-component' import flushChunks from 'webpack-flush-chunks' import ReactDOM from 'react-dom/server' @@ -204,11 +240,12 @@ function renderToHtml () => { } ``` + ## Preload You can preload the async component if there's a likelihood it will show soon: -```javascript +```js import universal from 'react-universal-component' const UniversalComponent = universal(import('./Foo')) @@ -228,7 +265,7 @@ export default class MyComponent extends React.Component { If your imported component has static methods like this: -```javascript +```js export default class MyComponent extends React.Component { static doSomething() {} render() {} @@ -237,7 +274,7 @@ export default class MyComponent extends React.Component { Then this will work: -```javascript +```js const MyUniversalComponent = universal(import('./MyComponent')) // render it @@ -246,7 +283,6 @@ const MyUniversalComponent = universal(import('./MyComponent')) // call this only after you're sure it has loaded MyUniversalComponent.doSomething() ``` - > NOTE: for imports using dynamic expressions, conflicting methods will be overwritten by the current component ## Props API @@ -258,54 +294,49 @@ MyUniversalComponent.doSomething() - `onError`: `error => handleError(error)` ### `isLoading` + `error`: - -You can pass `isLoading` and `error` props to the resulting component returned from the `universal` HoC. This has the convenient benefit of allowing you to continue to show the **_same_** `loading` component (or trigger the **_same_** `error` component) that is shown while your async component loads _AND_ while any data-fetching may be occuring in a parent HoC. That means less jank from unnecessary re-renders, and less work (DRY). +You can pass `isLoading` and `error` props to the resulting component returned from the `universal` HoC. This has the convenient benefit of allowing you to continue to show the ***same*** `loading` component (or trigger the ***same*** `error` component) that is shown while your async component loads *AND* while any data-fetching may be occuring in a parent HoC. That means less jank from unnecessary re-renders, and less work (DRY). Here's an example using Apollo: -```javascript +```js const UniversalUser = universal(import('./User')) -const User = ({ loading, error, user }) => ( +const User = ({ loading, error, user }) =>
-) -export default graphql( - gql` - query CurrentUser { - user { - id - name - } +export default graphql(gql` + query CurrentUser { + user { + id + name } - `, - { - props: ({ ownProps, data: { loading, error, user } }) => ({ - loading, - error, - user - }) } -)(User) +`, { + props: ({ ownProps, data: { loading, error, user } }) => ({ + loading, + error, + user, + }), +})(User) ``` - -> If it's not clear, the **_same_** `loading` component will show while both async aspects load, without flinching/re-rendering. And perhaps more importantly **they will be run in parallel**. +> If it's not clear, the ***same*** `loading` component will show while both async aspects load, without flinching/re-rendering. And perhaps more importantly **they will be run in parallel**. ### `onBefore` + `onAfter`: `onBefore/After` are callbacks called before and after the wrapped component loads/changes on both `componentWillMount` and `componentWillReceiveProps`. This enables you to display `loading` indicators elsewhere in the UI. -If the component is already cached or you're on the server, they will both be called **_back to back synchronously_**. They're both still called in this case for consistency. And they're both called before re-render to trigger the least amount of renders. Each receives an `info` object, giving you full flexibility in terms of deciding what to do. Here are the keys on it: +If the component is already cached or you're on the server, they will both be called ***back to back synchronously***. They're both still called in this case for consistency. And they're both called before re-render to trigger the least amount of renders. Each receives an `info` object, giving you full flexibility in terms of deciding what to do. Here are the keys on it: -- `isMount` _(whether the component just mounted)_ -- `isSync` _(whether the imported component is already available from previous usage and required synchronsouly)_ -- `isServer` _(very rarely will you want to do stuff on the server; note: server will always be sync)_ +- `isMount` *(whether the component just mounted)* +- `isSync` *(whether the imported component is already available from previous usage and required synchronsouly)* +- `isServer` *(very rarely will you want to do stuff on the server; note: server will always be sync)* `onAfter` is also passed a second argument containing the imported `Component`, which you can use to do things like call its static methods. -```javascript + +```js const UniversalComponent = universal(props => import(`./props.page`)) const MyComponent = ({ dispatch, isLoading }) => @@ -320,7 +351,7 @@ const MyComponent = ({ dispatch, isLoading }) => ``` -> Keep in mind if you call `setState` within these callbacks and they are called during `componentWillMount`, the `state` change will have no effect for that render. This is because the component is already in the middle of being rendered within the parent on which `this.setState` will be called. You can use _Redux_ to call `dispatch` and that will affect child components. However, it's best to use this primarily for setting up and tearing down loading state on the client, and nothing more. If you chose to use them on the server, make sure the client renders the same thing on first load or you will have checksum mismatches. One good thing is that if you use `setState`, it in fact won't cause checksum mismatches since it won't be called on the server or the first render on the client. It will be called on an instant subsequent render on the client and helpfully display errors where it counts. The same won't apply with `dispatch` which can affect children components, and therefore could lead to rendering different things on each side. +> Keep in mind if you call `setState` within these callbacks and they are called during `componentWillMount`, the `state` change will have no effect for that render. This is because the component is already in the middle of being rendered within the parent on which `this.setState` will be called. You can use *Redux* to call `dispatch` and that will affect child components. However, it's best to use this primarily for setting up and tearing down loading state on the client, and nothing more. If you chose to use them on the server, make sure the client renders the same thing on first load or you will have checksum mismatches. One good thing is that if you use `setState`, it in fact won't cause checksum mismatches since it won't be called on the server or the first render on the client. It will be called on an instant subsequent render on the client and helpfully display errors where it counts. The same won't apply with `dispatch` which can affect children components, and therefore could lead to rendering different things on each side. ### `onError` @@ -336,14 +367,14 @@ To use these together, simply wrap the CSS library's callback with clearChunks() ### Example with Aphrodite -```javascript +```js import { StyleSheetServer } from 'aphrodite' -import { clearChunks, flushChunkNames } from 'react-universal-component/server' +import { clearChunks, flushChunkNames } from "react-universal-component/server" import ReactDOM from 'react-dom/server' clearChunks() // similar for emotion, aphodite, glamor, glamorous -const { html, css } = StyleSheetServer.renderStatic(() => { +const { html, css} = StyleSheetServer.renderStatic(() => { return ReactDOM.renderToString(app) }) const chunkNames = flushChunkNames() @@ -359,10 +390,10 @@ Some data-fetching libraries require an additional step which walks the render t ### Example with react-apollo and Aphrodite -```javascript -import { getDataFromTree } from 'react-apollo' +```js +import { getDataFromTree } from "react-apollo" import { StyleSheetServer } from 'aphrodite' -import { clearChunks, flushChunkNames } from 'react-universal-component/server' +import { clearChunks, flushChunkNames } from "react-universal-component/server" import ReactDOM from 'react-dom/server' const app = ( @@ -377,7 +408,7 @@ getDataFromTree(app).then(() => { // This is safe. clearChunks() - const { html, css } = StyleSheetServer.renderStatic(() => { + const { html, css} = StyleSheetServer.renderStatic(() => { return ReactDOM.renderToString(app) }) const chunkNames = flushChunkNames() @@ -387,7 +418,6 @@ getDataFromTree(app).then(() => { ``` ## Universal Demo - 🍾🍾🍾 **[faceyspacey/universal-demo](https://github.com/faceyspacey/universal-demo)** πŸš€πŸš€πŸš€ ```bash @@ -401,14 +431,15 @@ yarn start We use [commitizen](https://github.com/commitizen/cz-cli), so run `npm run cm` to make commits. A command-line form will appear, requiring you answer a few questions to automatically produce a nicely formatted commit. Releases, semantic version numbers, tags, changelogs and publishing to NPM will automatically be handled based on these commits thanks to [semantic-release](https://github.com/semantic-release/semantic-release). Be good. + ## Tests Reviewing a module's tests are a great way to get familiar with it. It's direct insight into the capabilities of the given module (if the tests are thorough). What's even better is a screenshot of the tests neatly organized and grouped (you know the whole "a picture says a thousand words" thing). -Below is a screenshot of this module's tests running in [Wallaby](https://wallabyjs.com) _("An Integrated Continuous Testing Tool for JavaScript")_ which everyone in the React community should be using. It's fantastic and has taken my entire workflow to the next level. It re-runs your tests on every change along with comprehensive logging, bi-directional linking to your IDE, in-line code coverage indicators, **and even snapshot comparisons + updates for Jest!** I requestsed that feature by the way :). It's basically a substitute for live-coding that inspires you to test along your journey. +Below is a screenshot of this module's tests running in [Wallaby](https://wallabyjs.com) *("An Integrated Continuous Testing Tool for JavaScript")* which everyone in the React community should be using. It's fantastic and has taken my entire workflow to the next level. It re-runs your tests on every change along with comprehensive logging, bi-directional linking to your IDE, in-line code coverage indicators, **and even snapshot comparisons + updates for Jest!** I requestsed that feature by the way :). It's basically a substitute for live-coding that inspires you to test along your journey. -![require-universal-module wallaby tests screenshot](./tests-screenshot-1.png) ![require-universal-module wallaby tests screenshot](./tests-screenshot-2.png) +![require-universal-module wallaby tests screenshot](./tests-screenshot-1.png) +![require-universal-module wallaby tests screenshot](./tests-screenshot-2.png) ## More from FaceySpacey in Reactlandia - -- [redux-first-router](https://github.com/faceyspacey/redux-first-router). It's made to work perfectly with _Universal_. Together they comprise our _"frameworkless"_ Redux-based approach to what Next.js does (splitting, SSR, prefetching, routing). +- [redux-first-router](https://github.com/faceyspacey/redux-first-router). It's made to work perfectly with *Universal*. Together they comprise our *"frameworkless"* Redux-based approach to what Next.js does (splitting, SSR, prefetching, routing).