@shopify/react-html
Advanced tools
Comparing version 4.0.1 to 5.0.0
@@ -1,3 +0,2 @@ | ||
import Script, { Props as ScriptProps } from './Script'; | ||
import Style, { Props as StyleProps } from './Style'; | ||
export { Script, ScriptProps, Style, StyleProps }; | ||
export { default as Style } from './Style'; | ||
export { default as Script } from './Script'; |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var Script_1 = __importDefault(require("./Script")); | ||
var Style_1 = require("./Style"); | ||
exports.Style = Style_1.default; | ||
var Script_1 = require("./Script"); | ||
exports.Script = Script_1.default; | ||
var Style_1 = __importDefault(require("./Style")); | ||
exports.Style = Style_1.default; |
@@ -1,5 +0,1 @@ | ||
import HTML from './HTML'; | ||
export { Script, Style } from './components'; | ||
export declare const DOCTYPE = "<!DOCTYPE html>"; | ||
export { Props } from './HTML'; | ||
export default HTML; | ||
export * from './common'; |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
function __export(m) { | ||
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; | ||
} | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var HTML_1 = __importDefault(require("./HTML")); | ||
var components_1 = require("./components"); | ||
exports.Script = components_1.Script; | ||
exports.Style = components_1.Style; | ||
exports.DOCTYPE = '<!DOCTYPE html>'; | ||
exports.default = HTML_1.default; | ||
__export(require("./common")); |
{ | ||
"name": "@shopify/react-html", | ||
"version": "4.0.1", | ||
"version": "5.0.0", | ||
"license": "MIT", | ||
@@ -26,3 +26,3 @@ "description": "A component to render your react app with no static HTML.", | ||
"dependencies": { | ||
"@shopify/react-serialize": "^1.0.9", | ||
"@shopify/react-serialize": "^1.0.10", | ||
"react-helmet": "^5.2.0" | ||
@@ -29,0 +29,0 @@ }, |
234
README.md
@@ -5,3 +5,3 @@ # `@shopify/react-html` | ||
A component to render your React app with no static HTML. | ||
A collection of utilities for constructing an HTML document. | ||
@@ -16,93 +16,173 @@ ## Installation | ||
The `<HTML>` component serves as a top level wrapper for a react application, allowing you to avoid needing any kind of server side template, in favor of purely using `reactDom.renderToString`. | ||
This package exposes two entrypoints: | ||
```javascript | ||
- `@shopify/react-html`: which contains code that can be used on server and client. Code in your `app` and `client` directories should import from this entrypoint. | ||
- `@shopify/react-html/server`: which contains all of `react-html`, but also includes some features that are not safe to run in a browser context. Code in your `server` directory should import from this entrypoint. | ||
> Note: because this package creates an HTML document, this package is only for applications that are server rendered in Node. Rails apps generally have Rails perform the render of the HTML document, so they do not benefit from any part of this library. | ||
### In your server middleware | ||
Your server needs construct an HTML document. To do this, you can use the `Html` component and `render` function from `@shopify/react-html/server`: | ||
```tsx | ||
import * as React from 'react'; | ||
import {renderToString} from 'react-dom/server'; | ||
import {render, Html} from '@shopify/react-html/server'; | ||
import HTML, {DOCTYPE} from '@shopify/react-html'; | ||
import MyApp from '../app'; | ||
import App from '../app'; | ||
export default (ctx, next) => { | ||
// we have to prepend DOCTYPE to serve valid HTML | ||
ctx.body = DOCTYPE + renderToString( | ||
<HTML> | ||
<MyApp /> | ||
</HTML> | ||
export default function middleware(ctx) { | ||
ctx.body = render( | ||
<Html> | ||
<App /> | ||
</Html>, | ||
); | ||
} | ||
``` | ||
await next(); | ||
The component will automatically propagate any usage of the [`react-helmet` library](https://github.com/nfl/react-helmet) in your app’s content to manipulate the title or other top level HTML or HEAD attributes. If you want to make use of the serialization techniques [documented below](#in-your-app-code), you must also construct a `Manager` instance, pass it to a `<Provider />` component, and call `@shopify/react-effect`’s `extract` method: | ||
```tsx | ||
// in App.tsx | ||
import {Manager, Provider} from '@shopify/react-html'; | ||
function App({htmlManager}: {htmlManager: Manager}) { | ||
return <Provider manager={htmlManager}>Hello world!</Provider>; | ||
} | ||
``` | ||
Due to [limitations in React’s implementation of HTML](https://github.com/facebook/react/issues/1035), you still need to prepend the `<!DOCTYPE html>` directive. To assist with this the module also exports a `DOCTYPE` constant. | ||
```tsx | ||
// Somewhere in your server | ||
import {extract} from '@shopify/react-effect/server'; | ||
import {render, Html, Manager} from '@shopify/react-html/server'; | ||
The component will automatically propagate any usage of the `react-helmet` module in your app’s content to manipulate the title or other top level HTML or HEAD attributes. | ||
export default function middleware(ctx) { | ||
const manager = new Manager(); | ||
const app = <App htmlManager={manager} />; | ||
## Interface | ||
await extract(app); | ||
```typescript | ||
export interface Props { | ||
children?: React.ReactNode; | ||
styles?: Asset[]; | ||
scripts?: Asset[]; | ||
blockingScripts?: Asset[]; | ||
headData?: {[id: string]: any}; | ||
data?: {[id: string]: any}; | ||
hideForInitialLoad?: boolean; | ||
ctx.body = render(<Html manager={manager}>{app}</Html>); | ||
} | ||
``` | ||
interface Asset { | ||
path: string; | ||
integrity?: string; | ||
} | ||
You can also use the `Script`, `Style`, and `Serialize` components detailed in the [API reference](#api-reference) to manually construct a variety of tags, which you will typically insert into the document with the [`Html` component’s `headMarkup` and `bodyMarkup` props](#html). | ||
interface Browser { | ||
userAgent: string; | ||
supported: boolean; | ||
} | ||
### In your client entrypoint | ||
Your client needs to rehydrate the React application. In development, it also needs to remove some of the temporary markup we create to prevent flashes of unstyled content. To do so, use the `showPage` function exported from `@shopify/react-html`: | ||
```tsx | ||
import {hydrate} from 'react-dom'; | ||
import {showPage} from '@shopify/react-html'; | ||
import App from '../app'; | ||
hydrate(<App />, document.querySelector('#app')); | ||
showPage(); | ||
``` | ||
### Basic props | ||
You do not need to create a `Manager`/ `Provider` component on the client. | ||
Most simple applications will only need these basic properties. | ||
### In your application code | ||
**children** | ||
The children to be rendered inside the `#app` div. | ||
Some parts of your application code may have some form of state that must be rehydrated when the server-rendered page is rehydrated on the client. To do so, application code can use the `createSerializer` function exported from `@shopify/react-html`. | ||
**styles** | ||
Descriptors for any style tags you want to include in the HEAD of the document. | ||
`createSerializer()` accepts a single string argument for the identifier to use; this will help you find the serialized `script` tag if you need to debug later on. It also accepts a generic type argument for the type of the data that will be serialized/ available after deserialization. | ||
**scripts** | ||
Descriptors for any script tags you want to include in your document. All scripts passed to this property will be deferred by appending them to the end of the document. We encourage this as a default, although you may use `blockingScripts` for any scripts that must be included in the HEAD of the document. | ||
The function returns a pair of components: | ||
**blockingScripts** | ||
Descriptors for any script tags you want to include in the HEAD of the document. These will block HTML parsing until they are evaluated, so use them carefully. | ||
```tsx | ||
const {Serialize, WithSerialized} = createSerializer<string>('MyData'); | ||
**hideForInitialLoad** | ||
Sets the body contents to be hidden for the initial render. Use this when injecting stylesheets dynamically in development in order to prevent a flash of unstyled content. | ||
// Would create components with the following types: | ||
### Serializers | ||
function Serialize({data}: {data(): string}): null; | ||
function WithSerialized({ | ||
children, | ||
}: { | ||
children(data: string | undefined): React.ReactNode; | ||
}): React.ReactNode; | ||
``` | ||
These props are useful for more complex applications that want to synchronize Redux, Apollo, translation, or any other data across the network boundary. These props are stringified into the DOM using (`@shopify/react-serialize`)[https://github.com/Shopify/quilt/blob/master/packages/react-serialize/README.md]. | ||
The general pattern for using these components is to render the `WithSerialized` component as the top-most child of a component responsible for managing this state. Within the render prop, construct whatever stateful store or manager you need, using the data that was retrieved in cases where the serialization was found (on the browser, or on subsequent server renders). Finally, render the UI that depends on that stateful part, and a `Serialize` component that extracts the part that you you need to communicate between server and client. | ||
**headData** | ||
Any serializable data that needs to be available from the DOM when the `synchronousScripts` are run. | ||
Here is a complete example, using `@shopify/react-i18n`’s support for async translations as the data that needs to be serialized: | ||
**data** | ||
Any serializable data that needs to be available from the DOM when the `deferredScripts` are run. | ||
```tsx | ||
import {createSerializer} from '@shopify/react-html'; | ||
import {Provider, Manager} from '@shopify/react-i18n'; | ||
## Asset Components | ||
interface Props { | ||
locale: string; | ||
children: React.ReactNode; | ||
} | ||
This module also exports the asset components the `<HTML />` component uses internally for its script and style props. | ||
const {Serialize, WithSerialized} = createSerializer< | ||
ReturnType<Manager['extract']> | ||
>('i18n'); | ||
```ts | ||
import {Style, Script} from '@shopify/react-html'; | ||
export default function I18n({locale, children}: Props) { | ||
return ( | ||
<WithSerialized> | ||
{data => { | ||
const manager = new Manager( | ||
{locale: data ? data.locale : locale}, | ||
data && data.translations, | ||
); | ||
return ( | ||
<> | ||
<Provider manager={manager}>{children}</Provider> | ||
<Serialize | ||
data={() => ({ | ||
locale: manager.details.locale, | ||
translations: manager.extract(), | ||
})} | ||
/> | ||
</> | ||
); | ||
}} | ||
</WithSerialized> | ||
); | ||
} | ||
``` | ||
### Style | ||
The rationale for this approach to handling serialization is available in [our original proposal](https://github.com/Shopify/web-foundation/blob/master/Proposals/02%20-%20Serialization%20in%20application%20code.md). | ||
The `<Style />` component lets you render `<link>` tags in your document dynamically as part of your react app. | ||
## API reference | ||
```ts | ||
### `<Html />` | ||
The `<Html>` component serves as a top level wrapper for a React application, allowing you to avoid needing any kind of server-side template. It is only available from the server entrypoint of this package (`@shopify/react-html/server`). The `Html` component accepts the following props: | ||
- `manager`: a `Manager` instance. When provided, the `Html` component will extract all the information from this object and place it in an appropriate place in the document. | ||
- `children`: the application. It will be rendered to a string and placed inside a `div` with an ID of `app`. | ||
- `locale`: the language to use for the HTML `lang` attribute. | ||
- `styles`: descriptors for any style tags you want to include in the HEAD of the document. | ||
- `scripts`: descriptors for any script tags you want to include in your document. All scripts passed to this property will be deferred by appending them to the end of the document. We encourage this as a default because it improves the initial rendering performance of your page. | ||
- `blockingScripts`: descriptors for any script tags you want to include in the HEAD of the document. These will block HTML parsing until they are evaluated, so use them carefully. | ||
- `headMarkup`: additional JSX to be embedded in the head of the document (after styles, but before blocking scripts). | ||
- `bodyMarkup`: additional JSX to be embedded in the body of the document (before serialization markup and deferred scripts). | ||
```tsx | ||
import {Html} from '@shopify/react-html/server'; | ||
const html = ( | ||
<Html | ||
locale="fr" | ||
styles={[{path: '/style.css'}]} | ||
scripts={[{path: '/script.js'}]} | ||
> | ||
<App /> | ||
</Html> | ||
); | ||
``` | ||
### `<Style />` | ||
The `<Style />` component lets you render `<link>` tags in your document dynamically as part of your react app. It supports all of the props of a basic `link` tag, but forces some properties to be the values needed for a stylesheet. In general, prefer the `styles` prop of the `Html` component instead of using this component explicitly. | ||
```tsx | ||
import {Style} from '@shopify/react-html'; | ||
<Style | ||
@@ -112,10 +192,12 @@ href="./some-style.css" | ||
crossOrigin="anonymous" | ||
/> | ||
/>; | ||
``` | ||
### Script | ||
### `<Script />` | ||
The `<Script />` component lets you render `<script>` tags in your document dynamically as part of your react app. | ||
The `<Script />` component lets you render `<script>` tags in your document dynamically as part of your react app. It supports all the props of a basic `script` tag. In general, prefer the `scripts` prop of the `Html` component instead of using this component explicitly. | ||
```ts | ||
```tsx | ||
import {Script} from '@shopify/react-html'; | ||
<Script | ||
@@ -125,3 +207,29 @@ src="./some-script.js" | ||
crossOrigin="anonymous" | ||
/> | ||
/>; | ||
``` | ||
### `<Serialize />` | ||
The Serialize component takes care of rendering a `script` tag with a serialized version of the `data` prop. It is provided for incremental adoption of the `createSerializer()` method of generating serializations [documented above](#in-your-app-code). | ||
### `render()` | ||
The `render()` function creates a stringified version of the HTML document with an appropriate DOCTYPE. It is only available from the server entrypoint of this package (`@shopify/react-html/server`). | ||
```tsx | ||
import {render, Html} from '@shopify/react-html/sever'; | ||
const markup = render(<Html>Hello world!</Html>); | ||
``` | ||
### `showPage()` | ||
This function encapsulates the logic for showing the page in development, where it is hidden on the initial render by default. You must call this function from your client entry point, usually right after hydrating your React app. It returns a promise that resolves after the document is guaranteed to be visible. | ||
### `getSerialized<Data>()` | ||
To help in migration, this function can imperatively return the parsed value of a serialization. It returns the data cast to whatever is passed for `Data`. It should only be called on the client. | ||
## Migration | ||
- [Migrating from 4.x to 5.x](../documentation/migration-version-4-to-5.md) |
@@ -1,4 +0,2 @@ | ||
import Script, {Props as ScriptProps} from './Script'; | ||
import Style, {Props as StyleProps} from './Style'; | ||
export {Script, ScriptProps, Style, StyleProps}; | ||
export {default as Style} from './Style'; | ||
export {default as Script} from './Script'; |
@@ -1,8 +0,1 @@ | ||
import HTML from './HTML'; | ||
export {Script, Style} from './components'; | ||
export const DOCTYPE = '<!DOCTYPE html>'; | ||
export {Props} from './HTML'; | ||
export default HTML; | ||
export * from './common'; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
51610
52
1034
232
1
1