@shopify/react-html
Advanced tools
Comparing version 7.1.2 to 8.0.0-beta.1
@@ -13,8 +13,2 @@ # Changelog | ||
## 7.1.2 - 2019-03-02 | ||
### Fixed | ||
- Removed the `title` and `favicon` props from `<Html />` because they did not have any effect on the rendered markup. Developers should include `<Title />` and `<Favicon />` components themselves instead. | ||
## 7.1.1 - 2019-02-27 | ||
@@ -21,0 +15,0 @@ |
export * from './components'; | ||
export { default as Manager, EFFECT_ID } from './manager'; | ||
export { Provider } from './context'; | ||
export { HtmlContext, HtmlProvider } from './context'; | ||
export { showPage, getSerialized } from './utilities'; | ||
export { createSerializer } from './serializer'; | ||
export { useDomEffect } from './hook'; | ||
export { createSerializer, useSerialized } from './serializer'; |
@@ -9,7 +9,11 @@ "use strict"; | ||
var context_1 = require("./context"); | ||
exports.Provider = context_1.Provider; | ||
exports.HtmlContext = context_1.HtmlContext; | ||
exports.HtmlProvider = context_1.HtmlProvider; | ||
var utilities_1 = require("./utilities"); | ||
exports.showPage = utilities_1.showPage; | ||
exports.getSerialized = utilities_1.getSerialized; | ||
var hook_1 = require("./hook"); | ||
exports.useDomEffect = hook_1.useDomEffect; | ||
var serializer_1 = require("./serializer"); | ||
exports.createSerializer = serializer_1.createSerializer; | ||
exports.useSerialized = serializer_1.useSerialized; |
import * as React from 'react'; | ||
declare type Props = React.HTMLProps<HTMLLinkElement>; | ||
export default function Link(props: Props): JSX.Element; | ||
export default function Link(props: Props): null; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var tslib_1 = require("tslib"); | ||
var React = tslib_1.__importStar(require("react")); | ||
var DomEffect_1 = tslib_1.__importDefault(require("./DomEffect")); | ||
var hook_1 = require("../hook"); | ||
function Link(props) { | ||
return (React.createElement(DomEffect_1.default, { key: JSON.stringify(props), perform: function (manager) { return manager.addLink(props); } })); | ||
hook_1.useDomEffect(function (manager) { return manager.addLink(props); }, [JSON.stringify(props)]); | ||
return null; | ||
} | ||
exports.default = Link; |
import * as React from 'react'; | ||
declare type Props = React.HTMLProps<HTMLMetaElement>; | ||
export default function Meta(props: Props): JSX.Element; | ||
export default function Meta(props: Props): null; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var tslib_1 = require("tslib"); | ||
var React = tslib_1.__importStar(require("react")); | ||
var DomEffect_1 = tslib_1.__importDefault(require("./DomEffect")); | ||
var hook_1 = require("../hook"); | ||
function Meta(props) { | ||
return (React.createElement(DomEffect_1.default, { key: JSON.stringify(props), perform: function (manager) { return manager.addMeta(props); } })); | ||
hook_1.useDomEffect(function (manager) { return manager.addMeta(props); }, [JSON.stringify(props)]); | ||
return null; | ||
} | ||
exports.default = Meta; |
@@ -8,4 +8,4 @@ "use strict"; | ||
function mountWithManager(element, manager) { | ||
return enzyme_1.mount(React.createElement(context_1.Provider, { manager: manager }, element)); | ||
return enzyme_1.mount(React.createElement(context_1.HtmlProvider, { manager: manager }, element)); | ||
} | ||
exports.mountWithManager = mountWithManager; |
@@ -1,6 +0,5 @@ | ||
/// <reference types="react" /> | ||
interface Props { | ||
children: string; | ||
} | ||
export default function Title({ children: title }: Props): JSX.Element; | ||
export default function Title({ children: title }: Props): null; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var tslib_1 = require("tslib"); | ||
var React = tslib_1.__importStar(require("react")); | ||
var DomEffect_1 = tslib_1.__importDefault(require("./DomEffect")); | ||
var hook_1 = require("../hook"); | ||
function Title(_a) { | ||
var title = _a.children; | ||
return React.createElement(DomEffect_1.default, { key: title, perform: function (manager) { return manager.addTitle(title); } }); | ||
hook_1.useDomEffect(function (manager) { return manager.addTitle(title); }, [title]); | ||
return null; | ||
} | ||
exports.default = Title; |
import * as React from 'react'; | ||
import Manager from './manager'; | ||
declare const Context: React.Context<Manager>; | ||
export declare const HtmlContext: React.Context<Manager | undefined>; | ||
interface Props { | ||
@@ -8,7 +8,3 @@ manager?: Manager; | ||
} | ||
declare class HtmlManagerProvider extends React.Component<Props> { | ||
private queuedUpdate?; | ||
componentDidMount(): void; | ||
render(): JSX.Element; | ||
} | ||
export { HtmlManagerProvider as Provider, Context }; | ||
export declare function HtmlProvider({ manager, children }: Props): JSX.Element; | ||
export {}; |
@@ -5,33 +5,24 @@ "use strict"; | ||
var React = tslib_1.__importStar(require("react")); | ||
var manager_1 = tslib_1.__importDefault(require("./manager")); | ||
var utilities_1 = require("./utilities"); | ||
var Context = React.createContext(new manager_1.default()); | ||
exports.Context = Context; | ||
var HtmlManagerProvider = /** @class */ (function (_super) { | ||
tslib_1.__extends(HtmlManagerProvider, _super); | ||
function HtmlManagerProvider() { | ||
return _super !== null && _super.apply(this, arguments) || this; | ||
} | ||
HtmlManagerProvider.prototype.componentDidMount = function () { | ||
var _this = this; | ||
var manager = this.props.manager; | ||
if (manager) { | ||
manager.subscribe(function (state) { | ||
if (_this.queuedUpdate) { | ||
cancelAnimationFrame(_this.queuedUpdate); | ||
} | ||
_this.queuedUpdate = requestAnimationFrame(function () { | ||
updateOnClient(state); | ||
_this.queuedUpdate = undefined; | ||
}); | ||
exports.HtmlContext = React.createContext(undefined); | ||
function HtmlProvider(_a) { | ||
var manager = _a.manager, children = _a.children; | ||
var queuedUpdate = React.useRef(null); | ||
React.useEffect(function () { | ||
if (manager == null) { | ||
return; | ||
} | ||
// eslint-disable-next-line consistent-return | ||
return manager.subscribe(function (state) { | ||
if (queuedUpdate.current) { | ||
cancelAnimationFrame(queuedUpdate.current); | ||
} | ||
queuedUpdate.current = requestAnimationFrame(function () { | ||
updateOnClient(state); | ||
}); | ||
} | ||
}; | ||
HtmlManagerProvider.prototype.render = function () { | ||
var _a = this.props, manager = _a.manager, children = _a.children; | ||
return manager ? (React.createElement(Context.Provider, { value: manager }, children)) : (React.createElement(React.Fragment, null, children)); | ||
}; | ||
return HtmlManagerProvider; | ||
}(React.Component)); | ||
exports.Provider = HtmlManagerProvider; | ||
}); | ||
}, [manager]); | ||
return (React.createElement(exports.HtmlContext.Provider, { value: manager }, children)); | ||
} | ||
exports.HtmlProvider = HtmlProvider; | ||
function updateOnClient(state) { | ||
@@ -38,0 +29,0 @@ var e_1, _a, e_2, _b, e_3, _c, e_4, _d; |
@@ -27,3 +27,3 @@ /// <reference types="react" /> | ||
}): void; | ||
subscribe(subscription: Subscription): void; | ||
subscribe(subscription: Subscription): () => void; | ||
addTitle(title: string): any; | ||
@@ -30,0 +30,0 @@ addMeta(meta: React.HTMLProps<HTMLMetaElement>): any; |
@@ -44,3 +44,7 @@ "use strict"; | ||
Manager.prototype.subscribe = function (subscription) { | ||
var _this = this; | ||
this.subscriptions.add(subscription); | ||
return function () { | ||
_this.subscriptions.delete(subscription); | ||
}; | ||
}; | ||
@@ -47,0 +51,0 @@ Manager.prototype.addTitle = function (title) { |
import * as React from 'react'; | ||
export declare const EXTRACT_ID: unique symbol; | ||
interface SerializeProps<T> { | ||
data(): T | Promise<T>; | ||
data: () => T | Promise<T>; | ||
} | ||
interface WithSerializedProps<T> { | ||
children(data?: T): React.ReactNode; | ||
children(data?: T): React.ReactElement<any>; | ||
} | ||
export declare function useSerialized<T>(id: string): [T | undefined, React.ComponentType<SerializeProps<T>>]; | ||
export declare function createSerializer<T>(id: string): { | ||
Serialize: ({ data }: SerializeProps<T>) => JSX.Element; | ||
WithSerialized: ({ children }: WithSerializedProps<T>) => JSX.Element; | ||
Serialize: ({ data }: SerializeProps<T>) => null; | ||
WithSerialized: ({ children }: WithSerializedProps<T>) => React.ReactElement<any, string | ((props: any) => React.ReactElement<any, string | any | (new (props: any) => React.Component<any, any, any>)> | null) | (new (props: any) => React.Component<any, any, any>)>; | ||
}; | ||
export {}; |
@@ -8,16 +8,46 @@ "use strict"; | ||
exports.EXTRACT_ID = Symbol('serialize'); | ||
function createSerializer(id) { | ||
function Serialize(_a) { | ||
var data = _a.data; | ||
return (React.createElement(context_1.Context.Consumer, null, function (manager) { return (React.createElement(react_effect_1.Effect, { kind: manager.effect, perform: function () { | ||
function useSerialized(id) { | ||
var manager = React.useContext(context_1.HtmlContext); | ||
var data = React.useMemo(function () { return manager && manager.getSerialization(id); }, [ | ||
id, | ||
manager, | ||
]); | ||
var Serialize = React.useMemo(function () { | ||
return function Serialize(_a) { | ||
var data = _a.data; | ||
var manager = React.useContext(context_1.HtmlContext); | ||
react_effect_1.useServerEffect(function () { | ||
var result = data(); | ||
var handleResult = manager.setSerialization.bind(manager, id); | ||
var handleResult = manager | ||
? manager.setSerialization.bind(manager, id) | ||
: noop; | ||
return typeof result === 'object' && isPromise(result) | ||
? result.then(handleResult) | ||
: handleResult(result); | ||
} })); })); | ||
}, manager && manager.effect); | ||
return null; | ||
}; | ||
}, [id, manager]); | ||
return [data, Serialize]; | ||
} | ||
exports.useSerialized = useSerialized; | ||
function createSerializer(id) { | ||
function Serialize(_a) { | ||
var data = _a.data; | ||
var manager = React.useContext(context_1.HtmlContext); | ||
react_effect_1.useServerEffect(function () { | ||
var result = data(); | ||
var handleResult = manager | ||
? manager.setSerialization.bind(manager, id) | ||
: noop; | ||
return typeof result === 'object' && isPromise(result) | ||
? result.then(handleResult) | ||
: handleResult(result); | ||
}, manager && manager.effect); | ||
return null; | ||
} | ||
function WithSerialized(_a) { | ||
var children = _a.children; | ||
return (React.createElement(context_1.Context.Consumer, null, function (manager) { return children(manager.getSerialization(id)); })); | ||
var manager = React.useContext(context_1.HtmlContext); | ||
return children(manager && manager.getSerialization(id)); | ||
} | ||
@@ -30,1 +60,2 @@ return { Serialize: Serialize, WithSerialized: WithSerialized }; | ||
} | ||
function noop() { } |
@@ -16,3 +16,5 @@ import * as React from 'react'; | ||
bodyMarkup?: React.ReactNode; | ||
favicon?: string; | ||
title?: string; | ||
} | ||
export default function Html({ manager, children, locale, blockingScripts, scripts, styles, headMarkup, bodyMarkup, }: Props): JSX.Element; | ||
export default function Html({ manager, children, locale, blockingScripts, scripts, styles, headMarkup, bodyMarkup, favicon, title, }: Props): JSX.Element; |
@@ -10,3 +10,3 @@ "use strict"; | ||
function Html(_a) { | ||
var manager = _a.manager, children = _a.children, _b = _a.locale, locale = _b === void 0 ? 'en' : _b, _c = _a.blockingScripts, blockingScripts = _c === void 0 ? [] : _c, _d = _a.scripts, scripts = _d === void 0 ? [] : _d, _e = _a.styles, styles = _e === void 0 ? [] : _e, _f = _a.headMarkup, headMarkup = _f === void 0 ? null : _f, _g = _a.bodyMarkup, bodyMarkup = _g === void 0 ? null : _g; | ||
var manager = _a.manager, children = _a.children, _b = _a.locale, locale = _b === void 0 ? 'en' : _b, _c = _a.blockingScripts, blockingScripts = _c === void 0 ? [] : _c, _d = _a.scripts, scripts = _d === void 0 ? [] : _d, _e = _a.styles, styles = _e === void 0 ? [] : _e, _f = _a.headMarkup, headMarkup = _f === void 0 ? null : _f, _g = _a.bodyMarkup, bodyMarkup = _g === void 0 ? null : _g, favicon = _a.favicon, title = _a.title; | ||
var _h; | ||
@@ -22,3 +22,4 @@ var markup = typeof children === 'string' ? children : server_1.renderToString(children); | ||
var managedProps = (_h = {}, _h[utilities_1.MANAGED_ATTRIBUTE] = true, _h); | ||
var titleMarkup = extracted && extracted.title ? (React.createElement("title", tslib_1.__assign({}, managedProps), extracted.title)) : null; | ||
var titleFallbackMarkup = title ? React.createElement(components_1.Title, null, title) : null; | ||
var titleMarkup = extracted && extracted.title ? (React.createElement("title", tslib_1.__assign({}, managedProps), extracted.title)) : (titleFallbackMarkup); | ||
var metaMarkup = extracted | ||
@@ -50,8 +51,10 @@ ? extracted.metas.map(function (metaProps, index) { return ( | ||
process.env.NODE_ENV === 'development' ? { visibility: 'hidden' } : undefined; | ||
var faviconMarkup = favicon ? React.createElement(components_1.Favicon, { source: favicon }) : null; | ||
return (React.createElement("html", { lang: locale }, | ||
React.createElement("head", null, | ||
React.createElement(components_1.Meta, { charSet: "utf-8" }), | ||
React.createElement(components_1.Meta, { httpEquiv: "X-UA-Compatible", content: "IE=edge" }), | ||
React.createElement(components_1.Meta, { name: "referrer", content: "never" }), | ||
faviconMarkup, | ||
titleMarkup, | ||
React.createElement("meta", { charSet: "utf-8" }), | ||
React.createElement("meta", { httpEquiv: "X-UA-Compatible", content: "IE=edge" }), | ||
React.createElement("meta", { name: "referrer", content: "never" }), | ||
metaMarkup, | ||
@@ -58,0 +61,0 @@ linkMarkup, |
{ | ||
"name": "@shopify/react-html", | ||
"version": "7.1.2", | ||
"version": "8.0.0-beta.1", | ||
"license": "MIT", | ||
@@ -26,3 +26,3 @@ "description": "A component to render your react app with no static HTML.", | ||
"dependencies": { | ||
"@shopify/react-effect": "^2.0.1", | ||
"@shopify/react-effect": "3.0.0-beta.1", | ||
"@shopify/react-serialize": "^1.0.12", | ||
@@ -29,0 +29,0 @@ "@shopify/useful-types": "^1.1.2", |
@@ -86,28 +86,15 @@ # `@shopify/react-html` | ||
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`. | ||
Some parts of your application code may have some form of state that must be rehydrated when the server-rendered page is loaded on the client. To do so, application code can use the `useSerialized` hook exported from `@shopify/react-html`. | ||
`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. | ||
`useSerialized()` 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. | ||
The function returns a pair of components: | ||
The hook returns an array where the first entry is the serialized data (or `undefined`, if it was not found), and the second entry is a component that accepts a `data` prop that is a function that returns the data to serialize (or a promise for that data). | ||
```tsx | ||
const {Serialize, WithSerialized} = createSerializer<string>('MyData'); | ||
> **Note:** providing a promise for the `data` prop has a catch if you are using `@shopify/react-effect` to extract the serializations in server rendering: it expects that you will only provide a promise for the serialization if it can’t be returned synchronously. If you always return a promise, `@shopify/react-effect` will assume it always needs to do another render of the tree, which will lead to an infinite loop. | ||
// Would create components with the following types: | ||
function Serialize({data}: {data(): string}): null; | ||
function WithSerialized({ | ||
children, | ||
}: { | ||
children(data: string | undefined): React.ReactNode; | ||
}): React.ReactNode; | ||
``` | ||
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. | ||
Here is a complete example, using `@shopify/react-i18n`’s support for async translations as the data that needs to be serialized: | ||
```tsx | ||
import {createSerializer} from '@shopify/react-html'; | ||
import {Provider, Manager} from '@shopify/react-i18n'; | ||
import {useSerialized} from '@shopify/react-html'; | ||
import {I18nContext, Manager} from '@shopify/react-i18n'; | ||
@@ -123,24 +110,26 @@ interface Props { | ||
interface Data { | ||
locale: string; | ||
translations: ReturnType<Manager['extract']>; | ||
} | ||
export default function I18n({locale, children}: Props) { | ||
const [serialized, Serialize] = useSerialized<Data>('i18n'); | ||
const {locale, translations} = serialized || {locale: explicitLocale}; | ||
const manager = new Manager({locale, fallbackLocale: 'en'}, translations); | ||
return ( | ||
<WithSerialized> | ||
{data => { | ||
const manager = new Manager( | ||
{locale: data ? data.locale : locale}, | ||
data && data.translations, | ||
); | ||
<> | ||
<I18nContext.Provider value={manager}>{children}</I18nContext.Provider> | ||
<Serialize | ||
data={() => { | ||
const getData = () => ({ | ||
locale: manager.details.locale, | ||
translations: manager.extract(), | ||
}); | ||
return ( | ||
<> | ||
<Provider manager={manager}>{children}</Provider> | ||
<Serialize | ||
data={() => ({ | ||
locale: manager.details.locale, | ||
translations: manager.extract(), | ||
})} | ||
/> | ||
</> | ||
); | ||
}} | ||
</WithSerialized> | ||
return manager.loading ? manager.resolve().then(getData) : getData(); | ||
}} | ||
/> | ||
</> | ||
); | ||
@@ -154,2 +143,10 @@ } | ||
### `useSerialized()` | ||
See the example above for a full exploration of `useSerialized`’s API. | ||
### `createSerializer()` | ||
`createSerializer` is a legacy API that has been deprecated by `useSerialized()`. For full documentation of this API, please refer to older versions of this document. `createSerializer` will be removed in the next major version of `@shopify/react-html`. | ||
### `<Html />` | ||
@@ -246,3 +243,3 @@ | ||
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). | ||
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 `useSerialized()` method of generating serializations [documented above](#in-your-app-code). | ||
@@ -249,0 +246,0 @@ ### `render()` |
export * from './components'; | ||
export {default as Manager, EFFECT_ID} from './manager'; | ||
export {Provider} from './context'; | ||
export {HtmlContext, HtmlProvider} from './context'; | ||
export {showPage, getSerialized} from './utilities'; | ||
export {createSerializer} from './serializer'; | ||
export {useDomEffect} from './hook'; | ||
export {createSerializer, useSerialized} from './serializer'; |
@@ -60,2 +60,5 @@ import {EffectKind} from '@shopify/react-effect'; | ||
this.subscriptions.add(subscription); | ||
return () => { | ||
this.subscriptions.delete(subscription); | ||
}; | ||
} | ||
@@ -62,0 +65,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
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
91894
2067
1
263
1
+ Added@shopify/react-effect@3.0.0-beta.1(transitive)
- Removed@shopify/react-effect@2.1.2(transitive)