@shopify/react-html
Advanced tools
Comparing version 8.0.0-beta.1 to 8.0.0
@@ -11,4 +11,29 @@ # Changelog | ||
<!-- ## Unreleased --> | ||
## Unreleased | ||
## 8.0.0 - 2019-04-08 | ||
This is a significant update that now requires at least React 16.8. You should refer to the [detailed migration guide](./documentation/migration-version-7-to-8.md) for additional guidance. | ||
### Changed | ||
- This library now requires at least React 16.8 ([#547](https://github.com/Shopify/quilt/pull/547)) | ||
- Removed `Provider`, exported the `HtmlContext` context object, exported a new `HeadUpdater` component for the client-side, and renamed `Manager` to `HtmlManager`. | ||
### Added | ||
- Added hook counterparts for most of the component APIs: `useSerialized`, `useTitle`, `useLink`, `useMeta`, `useFavicon`, and `usePreconnect` ([#547](https://github.com/Shopify/quilt/pull/547)) | ||
## 7.1.6 - 2019-03-27 | ||
### Changed | ||
- Deferred scripts are now rendered in `<head>` [#605](https://github.com/Shopify/quilt/pull/605/files) | ||
## 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 | ||
@@ -15,0 +40,0 @@ |
export * from './components'; | ||
export { default as Manager, EFFECT_ID } from './manager'; | ||
export { HtmlContext, HtmlProvider } from './context'; | ||
export { HtmlManager, EFFECT_ID } from './manager'; | ||
export { HtmlContext } from './context'; | ||
export { showPage, getSerialized } from './utilities'; | ||
export { useDomEffect } from './hook'; | ||
export { useDomEffect } from './hooks'; | ||
export { createSerializer, useSerialized } from './serializer'; |
@@ -6,14 +6,13 @@ "use strict"; | ||
var manager_1 = require("./manager"); | ||
exports.Manager = manager_1.default; | ||
exports.HtmlManager = manager_1.HtmlManager; | ||
exports.EFFECT_ID = manager_1.EFFECT_ID; | ||
var context_1 = require("./context"); | ||
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 hooks_1 = require("./hooks"); | ||
exports.useDomEffect = hooks_1.useDomEffect; | ||
var serializer_1 = require("./serializer"); | ||
exports.createSerializer = serializer_1.createSerializer; | ||
exports.useSerialized = serializer_1.useSerialized; |
@@ -15,3 +15,3 @@ /// <reference types="react" /> | ||
} | ||
export default function AppleHomeScreen({ icons, startUpImage }: Props): JSX.Element; | ||
export declare function AppleHomeScreen({ icons, startUpImage }: Props): JSX.Element; | ||
export {}; |
@@ -5,4 +5,4 @@ "use strict"; | ||
var React = tslib_1.__importStar(require("react")); | ||
var Meta_1 = tslib_1.__importDefault(require("./Meta")); | ||
var Link_1 = tslib_1.__importDefault(require("./Link")); | ||
var Meta_1 = require("./Meta"); | ||
var Link_1 = require("./Link"); | ||
var IconSize; | ||
@@ -18,11 +18,11 @@ (function (IconSize) { | ||
var size = _a.size, url = _a.url; | ||
return (React.createElement(Link_1.default, { key: size, rel: "apple-touch-icon", sizes: size + "x" + size, href: url })); | ||
return (React.createElement(Link_1.Link, { key: size, rel: "apple-touch-icon", sizes: size + "x" + size, href: url })); | ||
}); | ||
var startUpImageMarkup = startUpImage ? (React.createElement(Link_1.default, { rel: "apple-touch-startup-image", href: startUpImage })) : null; | ||
var startUpImageMarkup = startUpImage ? (React.createElement(Link_1.Link, { rel: "apple-touch-startup-image", href: startUpImage })) : null; | ||
return (React.createElement(React.Fragment, null, | ||
React.createElement(Meta_1.default, { name: "apple-mobile-web-app-capable", content: "yes" }), | ||
React.createElement(Meta_1.default, { name: "apple-mobile-web-app-status-bar-style", content: "black" }), | ||
React.createElement(Meta_1.Meta, { name: "apple-mobile-web-app-capable", content: "yes" }), | ||
React.createElement(Meta_1.Meta, { name: "apple-mobile-web-app-status-bar-style", content: "black" }), | ||
iconsMarkup, | ||
startUpImageMarkup)); | ||
} | ||
exports.default = AppleHomeScreen; | ||
exports.AppleHomeScreen = AppleHomeScreen; |
@@ -1,6 +0,5 @@ | ||
/// <reference types="react" /> | ||
interface Props { | ||
source: string; | ||
} | ||
export default function Favicon({ source }: Props): JSX.Element; | ||
export declare function Favicon({ source }: Props): null; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var tslib_1 = require("tslib"); | ||
var React = tslib_1.__importStar(require("react")); | ||
var Link_1 = tslib_1.__importDefault(require("./Link")); | ||
var hooks_1 = require("../hooks"); | ||
function Favicon(_a) { | ||
var source = _a.source; | ||
return React.createElement(Link_1.default, { rel: "shortcut icon", type: "image/x-icon", href: source }); | ||
hooks_1.useFavicon(source); | ||
return null; | ||
} | ||
exports.default = Favicon; | ||
exports.Favicon = Favicon; |
@@ -1,9 +0,10 @@ | ||
export { default as Favicon } from './Favicon'; | ||
export { default as Link } from './Link'; | ||
export { default as Meta } from './Meta'; | ||
export { default as Preconnect } from './Preconnect'; | ||
export { default as Script } from './Script'; | ||
export { default as Style } from './Style'; | ||
export { default as Title } from './Title'; | ||
export { default as Responsive } from './Responsive'; | ||
export { default as AppleHomeScreen } from './AppleHomeScreen'; | ||
export { Favicon } from './Favicon'; | ||
export { Link } from './Link'; | ||
export { Meta } from './Meta'; | ||
export { Preconnect } from './Preconnect'; | ||
export { Script } from './Script'; | ||
export { Style } from './Style'; | ||
export { Title } from './Title'; | ||
export { Responsive } from './Responsive'; | ||
export { AppleHomeScreen } from './AppleHomeScreen'; | ||
export { HeadUpdater } from './HeadUpdater'; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var Favicon_1 = require("./Favicon"); | ||
exports.Favicon = Favicon_1.default; | ||
exports.Favicon = Favicon_1.Favicon; | ||
var Link_1 = require("./Link"); | ||
exports.Link = Link_1.default; | ||
exports.Link = Link_1.Link; | ||
var Meta_1 = require("./Meta"); | ||
exports.Meta = Meta_1.default; | ||
exports.Meta = Meta_1.Meta; | ||
var Preconnect_1 = require("./Preconnect"); | ||
exports.Preconnect = Preconnect_1.default; | ||
exports.Preconnect = Preconnect_1.Preconnect; | ||
var Script_1 = require("./Script"); | ||
exports.Script = Script_1.default; | ||
exports.Script = Script_1.Script; | ||
var Style_1 = require("./Style"); | ||
exports.Style = Style_1.default; | ||
exports.Style = Style_1.Style; | ||
var Title_1 = require("./Title"); | ||
exports.Title = Title_1.default; | ||
exports.Title = Title_1.Title; | ||
var Responsive_1 = require("./Responsive"); | ||
exports.Responsive = Responsive_1.default; | ||
exports.Responsive = Responsive_1.Responsive; | ||
var AppleHomeScreen_1 = require("./AppleHomeScreen"); | ||
exports.AppleHomeScreen = AppleHomeScreen_1.default; | ||
exports.AppleHomeScreen = AppleHomeScreen_1.AppleHomeScreen; | ||
var HeadUpdater_1 = require("./HeadUpdater"); | ||
exports.HeadUpdater = HeadUpdater_1.HeadUpdater; |
import * as React from 'react'; | ||
declare type Props = React.HTMLProps<HTMLLinkElement>; | ||
export default function Link(props: Props): null; | ||
export declare function Link(props: Props): null; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var hook_1 = require("../hook"); | ||
var hooks_1 = require("../hooks"); | ||
function Link(props) { | ||
hook_1.useDomEffect(function (manager) { return manager.addLink(props); }, [JSON.stringify(props)]); | ||
hooks_1.useLink(props); | ||
return null; | ||
} | ||
exports.default = Link; | ||
exports.Link = Link; |
import * as React from 'react'; | ||
declare type Props = React.HTMLProps<HTMLMetaElement>; | ||
export default function Meta(props: Props): null; | ||
export declare function Meta(props: Props): null; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var hook_1 = require("../hook"); | ||
var hooks_1 = require("../hooks"); | ||
function Meta(props) { | ||
hook_1.useDomEffect(function (manager) { return manager.addMeta(props); }, [JSON.stringify(props)]); | ||
hooks_1.useMeta(props); | ||
return null; | ||
} | ||
exports.default = Meta; | ||
exports.Meta = Meta; |
@@ -1,6 +0,5 @@ | ||
/// <reference types="react" /> | ||
interface Props { | ||
source: string; | ||
} | ||
export default function Preconnect({ source }: Props): JSX.Element; | ||
export declare function Preconnect({ source }: Props): null; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var tslib_1 = require("tslib"); | ||
var React = tslib_1.__importStar(require("react")); | ||
var Link_1 = tslib_1.__importDefault(require("./Link")); | ||
var hooks_1 = require("../hooks"); | ||
function Preconnect(_a) { | ||
var source = _a.source; | ||
return React.createElement(Link_1.default, { rel: "dns-prefetch preconnect", href: source }); | ||
hooks_1.usePreconnect(source); | ||
return null; | ||
} | ||
exports.default = Preconnect; | ||
exports.Preconnect = Preconnect; |
@@ -1,2 +0,1 @@ | ||
/// <reference types="react" /> | ||
interface Props { | ||
@@ -6,3 +5,3 @@ coverNotch?: boolean; | ||
} | ||
export default function Responsive({ coverNotch, allowPinchToZoom, }: Props): JSX.Element; | ||
export declare function Responsive({ coverNotch, allowPinchToZoom, }: Props): null; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var tslib_1 = require("tslib"); | ||
var React = tslib_1.__importStar(require("react")); | ||
var Meta_1 = tslib_1.__importDefault(require("./Meta")); | ||
var hooks_1 = require("../hooks"); | ||
function Responsive(_a) { | ||
@@ -15,4 +13,8 @@ var _b = _a.coverNotch, coverNotch = _b === void 0 ? true : _b, _c = _a.allowPinchToZoom, allowPinchToZoom = _c === void 0 ? true : _c; | ||
} | ||
return React.createElement(Meta_1.default, { name: "viewport", content: viewportParts.join(', ') }); | ||
hooks_1.useMeta({ | ||
name: 'viewport', | ||
content: viewportParts.join(', '), | ||
}); | ||
return null; | ||
} | ||
exports.default = Responsive; | ||
exports.Responsive = Responsive; |
@@ -5,2 +5,2 @@ import * as React from 'react'; | ||
} | ||
export default function Script(props: Props): JSX.Element; | ||
export declare function Script(props: Props): JSX.Element; |
@@ -8,2 +8,2 @@ "use strict"; | ||
} | ||
exports.default = Script; | ||
exports.Script = Script; |
@@ -5,2 +5,2 @@ import * as React from 'react'; | ||
} | ||
export default function Style(props: Props): JSX.Element; | ||
export declare function Style(props: Props): JSX.Element; |
@@ -8,2 +8,2 @@ "use strict"; | ||
} | ||
exports.default = Style; | ||
exports.Style = Style; |
import * as React from 'react'; | ||
import Manager from '../../manager'; | ||
export declare function mountWithManager(element: React.ReactElement<any>, manager: Manager): import("enzyme").ReactWrapper<any, any>; | ||
import { HtmlManager } from '../../manager'; | ||
export declare function mountWithManager(element: React.ReactElement<any>, manager: HtmlManager): import("@shopify/react-testing/dist/root").Root<any>; |
@@ -5,7 +5,7 @@ "use strict"; | ||
var React = tslib_1.__importStar(require("react")); | ||
var enzyme_1 = require("enzyme"); | ||
var react_testing_1 = require("@shopify/react-testing"); | ||
var context_1 = require("../../context"); | ||
function mountWithManager(element, manager) { | ||
return enzyme_1.mount(React.createElement(context_1.HtmlProvider, { manager: manager }, element)); | ||
return react_testing_1.mount(React.createElement(context_1.HtmlContext.Provider, { value: manager }, element)); | ||
} | ||
exports.mountWithManager = mountWithManager; |
interface Props { | ||
children: string; | ||
} | ||
export default function Title({ children: title }: Props): null; | ||
export declare function Title({ children: title }: Props): null; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var hook_1 = require("../hook"); | ||
var hooks_1 = require("../hooks"); | ||
function Title(_a) { | ||
var title = _a.children; | ||
hook_1.useDomEffect(function (manager) { return manager.addTitle(title); }, [title]); | ||
hooks_1.useTitle(title); | ||
return null; | ||
} | ||
exports.default = Title; | ||
exports.Title = Title; |
import * as React from 'react'; | ||
import Manager from './manager'; | ||
export declare const HtmlContext: React.Context<Manager | undefined>; | ||
interface Props { | ||
manager?: Manager; | ||
children: React.ReactNode; | ||
} | ||
export declare function HtmlProvider({ manager, children }: Props): JSX.Element; | ||
export {}; | ||
import { HtmlManager } from './manager'; | ||
export declare const HtmlContext: React.Context<HtmlManager>; |
@@ -5,151 +5,3 @@ "use strict"; | ||
var React = tslib_1.__importStar(require("react")); | ||
var utilities_1 = require("./utilities"); | ||
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); | ||
}); | ||
}); | ||
}, [manager]); | ||
return (React.createElement(exports.HtmlContext.Provider, { value: manager }, children)); | ||
} | ||
exports.HtmlProvider = HtmlProvider; | ||
function updateOnClient(state) { | ||
var e_1, _a, e_2, _b, e_3, _c, e_4, _d; | ||
var title = state.title, metas = state.metas, links = state.links; | ||
var titleElement = document.querySelector('title'); | ||
if (title == null) { | ||
if (titleElement) { | ||
titleElement.remove(); | ||
} | ||
} | ||
else { | ||
if (titleElement == null) { | ||
titleElement = document.createElement('title'); | ||
document.head.appendChild(titleElement); | ||
} | ||
titleElement.setAttribute(utilities_1.MANAGED_ATTRIBUTE, 'true'); | ||
titleElement.textContent = title; | ||
} | ||
var fragment = document.createDocumentFragment(); | ||
var oldMetas = Array.from(document.head.querySelectorAll("meta[" + utilities_1.MANAGED_ATTRIBUTE + "]")); | ||
var _loop_1 = function (meta) { | ||
var e_5, _a; | ||
var element = document.createElement('meta'); | ||
element.setAttribute(utilities_1.MANAGED_ATTRIBUTE, 'true'); | ||
try { | ||
for (var _b = tslib_1.__values(Object.entries(meta)), _c = _b.next(); !_c.done; _c = _b.next()) { | ||
var _d = tslib_1.__read(_c.value, 2), attribute = _d[0], value = _d[1]; | ||
element.setAttribute(attribute, value); | ||
} | ||
} | ||
catch (e_5_1) { e_5 = { error: e_5_1 }; } | ||
finally { | ||
try { | ||
if (_c && !_c.done && (_a = _b.return)) _a.call(_b); | ||
} | ||
finally { if (e_5) throw e_5.error; } | ||
} | ||
var matchingOldMetaIndex = oldMetas.findIndex(function (oldMeta) { | ||
return oldMeta.isEqualNode(element); | ||
}); | ||
if (matchingOldMetaIndex >= 0) { | ||
oldMetas.splice(matchingOldMetaIndex, 1); | ||
} | ||
else { | ||
fragment.appendChild(element); | ||
} | ||
}; | ||
try { | ||
for (var metas_1 = tslib_1.__values(metas), metas_1_1 = metas_1.next(); !metas_1_1.done; metas_1_1 = metas_1.next()) { | ||
var meta = metas_1_1.value; | ||
_loop_1(meta); | ||
} | ||
} | ||
catch (e_1_1) { e_1 = { error: e_1_1 }; } | ||
finally { | ||
try { | ||
if (metas_1_1 && !metas_1_1.done && (_a = metas_1.return)) _a.call(metas_1); | ||
} | ||
finally { if (e_1) throw e_1.error; } | ||
} | ||
var oldLinks = Array.from(document.head.querySelectorAll("link[" + utilities_1.MANAGED_ATTRIBUTE + "]")); | ||
var _loop_2 = function (link) { | ||
var e_6, _a; | ||
var element = document.createElement('link'); | ||
element.setAttribute(utilities_1.MANAGED_ATTRIBUTE, 'true'); | ||
try { | ||
for (var _b = tslib_1.__values(Object.entries(link)), _c = _b.next(); !_c.done; _c = _b.next()) { | ||
var _d = tslib_1.__read(_c.value, 2), attribute = _d[0], value = _d[1]; | ||
element.setAttribute(attribute, value); | ||
} | ||
} | ||
catch (e_6_1) { e_6 = { error: e_6_1 }; } | ||
finally { | ||
try { | ||
if (_c && !_c.done && (_a = _b.return)) _a.call(_b); | ||
} | ||
finally { if (e_6) throw e_6.error; } | ||
} | ||
var matchingOldLinkIndex = oldLinks.findIndex(function (oldLink) { | ||
return oldLink.isEqualNode(element); | ||
}); | ||
if (matchingOldLinkIndex >= 0) { | ||
oldLinks.splice(matchingOldLinkIndex, 1); | ||
} | ||
else { | ||
fragment.appendChild(element); | ||
} | ||
}; | ||
try { | ||
for (var links_1 = tslib_1.__values(links), links_1_1 = links_1.next(); !links_1_1.done; links_1_1 = links_1.next()) { | ||
var link = links_1_1.value; | ||
_loop_2(link); | ||
} | ||
} | ||
catch (e_2_1) { e_2 = { error: e_2_1 }; } | ||
finally { | ||
try { | ||
if (links_1_1 && !links_1_1.done && (_b = links_1.return)) _b.call(links_1); | ||
} | ||
finally { if (e_2) throw e_2.error; } | ||
} | ||
try { | ||
for (var oldLinks_1 = tslib_1.__values(oldLinks), oldLinks_1_1 = oldLinks_1.next(); !oldLinks_1_1.done; oldLinks_1_1 = oldLinks_1.next()) { | ||
var link = oldLinks_1_1.value; | ||
link.remove(); | ||
} | ||
} | ||
catch (e_3_1) { e_3 = { error: e_3_1 }; } | ||
finally { | ||
try { | ||
if (oldLinks_1_1 && !oldLinks_1_1.done && (_c = oldLinks_1.return)) _c.call(oldLinks_1); | ||
} | ||
finally { if (e_3) throw e_3.error; } | ||
} | ||
try { | ||
for (var oldMetas_1 = tslib_1.__values(oldMetas), oldMetas_1_1 = oldMetas_1.next(); !oldMetas_1_1.done; oldMetas_1_1 = oldMetas_1.next()) { | ||
var meta = oldMetas_1_1.value; | ||
meta.remove(); | ||
} | ||
} | ||
catch (e_4_1) { e_4 = { error: e_4_1 }; } | ||
finally { | ||
try { | ||
if (oldMetas_1_1 && !oldMetas_1_1.done && (_d = oldMetas_1.return)) _d.call(oldMetas_1); | ||
} | ||
finally { if (e_4) throw e_4.error; } | ||
} | ||
document.head.appendChild(fragment); | ||
} | ||
var manager_1 = require("./manager"); | ||
exports.HtmlContext = React.createContext(new manager_1.HtmlManager()); |
@@ -12,3 +12,3 @@ /// <reference types="react" /> | ||
export declare const EFFECT_ID: unique symbol; | ||
export default class Manager { | ||
export declare class HtmlManager { | ||
effect: EffectKind; | ||
@@ -15,0 +15,0 @@ private isServer; |
@@ -6,4 +6,4 @@ "use strict"; | ||
exports.EFFECT_ID = Symbol('html'); | ||
var Manager = /** @class */ (function () { | ||
function Manager(_a) { | ||
var HtmlManager = /** @class */ (function () { | ||
function HtmlManager(_a) { | ||
var _b = (_a === void 0 ? {} : _a).isServer, isServer = _b === void 0 ? typeof document === 'undefined' : _b; | ||
@@ -22,3 +22,3 @@ var _this = this; | ||
} | ||
Object.defineProperty(Manager.prototype, "state", { | ||
Object.defineProperty(HtmlManager.prototype, "state", { | ||
get: function () { | ||
@@ -35,3 +35,3 @@ var lastTitle = this.titles[this.titles.length - 1]; | ||
}); | ||
Manager.prototype.reset = function (_a) { | ||
HtmlManager.prototype.reset = function (_a) { | ||
var _b = (_a === void 0 ? {} : _a).includeSerializations, includeSerializations = _b === void 0 ? false : _b; | ||
@@ -46,3 +46,3 @@ this.titles = []; | ||
}; | ||
Manager.prototype.subscribe = function (subscription) { | ||
HtmlManager.prototype.subscribe = function (subscription) { | ||
var _this = this; | ||
@@ -54,3 +54,3 @@ this.subscriptions.add(subscription); | ||
}; | ||
Manager.prototype.addTitle = function (title) { | ||
HtmlManager.prototype.addTitle = function (title) { | ||
var titleObject = { title: title }; | ||
@@ -61,3 +61,3 @@ this.titles.push(titleObject); | ||
}; | ||
Manager.prototype.addMeta = function (meta) { | ||
HtmlManager.prototype.addMeta = function (meta) { | ||
this.metas.push(meta); | ||
@@ -67,3 +67,3 @@ this.updateSubscriptions(); | ||
}; | ||
Manager.prototype.addLink = function (link) { | ||
HtmlManager.prototype.addLink = function (link) { | ||
this.links.push(link); | ||
@@ -73,3 +73,3 @@ this.updateSubscriptions(); | ||
}; | ||
Manager.prototype.setSerialization = function (id, data) { | ||
HtmlManager.prototype.setSerialization = function (id, data) { | ||
if (this.isServer) { | ||
@@ -79,6 +79,6 @@ this.serializations.set(id, data); | ||
}; | ||
Manager.prototype.getSerialization = function (id) { | ||
HtmlManager.prototype.getSerialization = function (id) { | ||
return this.serializations.get(id); | ||
}; | ||
Manager.prototype.extract = function () { | ||
HtmlManager.prototype.extract = function () { | ||
return tslib_1.__assign({}, this.state, { serializations: tslib_1.__spread(this.serializations.entries()).map(function (_a) { | ||
@@ -92,3 +92,3 @@ var _b = tslib_1.__read(_a, 2), id = _b[0], data = _b[1]; | ||
}; | ||
Manager.prototype.updateSubscriptions = function () { | ||
HtmlManager.prototype.updateSubscriptions = function () { | ||
var e_1, _a; | ||
@@ -109,3 +109,3 @@ try { | ||
}; | ||
Manager.prototype.removeTitle = function (title) { | ||
HtmlManager.prototype.removeTitle = function (title) { | ||
var index = this.titles.indexOf(title); | ||
@@ -117,3 +117,3 @@ if (index >= 0) { | ||
}; | ||
Manager.prototype.removeMeta = function (meta) { | ||
HtmlManager.prototype.removeMeta = function (meta) { | ||
var index = this.metas.indexOf(meta); | ||
@@ -125,3 +125,3 @@ if (index >= 0) { | ||
}; | ||
Manager.prototype.removeLink = function (link) { | ||
HtmlManager.prototype.removeLink = function (link) { | ||
var index = this.links.indexOf(link); | ||
@@ -133,4 +133,4 @@ if (index >= 0) { | ||
}; | ||
return Manager; | ||
return HtmlManager; | ||
}()); | ||
exports.default = Manager; | ||
exports.HtmlManager = HtmlManager; |
@@ -7,6 +7,7 @@ "use strict"; | ||
var context_1 = require("./context"); | ||
var hooks_1 = require("./hooks"); | ||
exports.EXTRACT_ID = Symbol('serialize'); | ||
function useSerialized(id) { | ||
var manager = React.useContext(context_1.HtmlContext); | ||
var data = React.useMemo(function () { return manager && manager.getSerialization(id); }, [ | ||
var data = React.useMemo(function () { return manager.getSerialization(id); }, [ | ||
id, | ||
@@ -18,12 +19,11 @@ manager, | ||
var data = _a.data; | ||
var manager = React.useContext(context_1.HtmlContext); | ||
react_effect_1.useServerEffect(function () { | ||
hooks_1.useServerDomEffect(function (manager) { | ||
var result = data(); | ||
var handleResult = manager | ||
? manager.setSerialization.bind(manager, id) | ||
: noop; | ||
return typeof result === 'object' && isPromise(result) | ||
var handleResult = manager.setSerialization.bind(manager, id); | ||
return typeof result === 'object' && | ||
result != null && | ||
isPromise(result) | ||
? result.then(handleResult) | ||
: handleResult(result); | ||
}, manager && manager.effect); | ||
}); | ||
return null; | ||
@@ -41,9 +41,7 @@ }; | ||
var result = data(); | ||
var handleResult = manager | ||
? manager.setSerialization.bind(manager, id) | ||
: noop; | ||
return typeof result === 'object' && isPromise(result) | ||
var handleResult = manager.setSerialization.bind(manager, id); | ||
return typeof result === 'object' && result != null && isPromise(result) | ||
? result.then(handleResult) | ||
: handleResult(result); | ||
}, manager && manager.effect); | ||
}, manager ? manager.effect : undefined); | ||
return null; | ||
@@ -54,3 +52,3 @@ } | ||
var manager = React.useContext(context_1.HtmlContext); | ||
return children(manager && manager.getSerialization(id)); | ||
return children(manager.getSerialization(id)); | ||
} | ||
@@ -63,2 +61,1 @@ return { Serialize: Serialize, WithSerialized: WithSerialized }; | ||
} | ||
function noop() { } |
import * as React from 'react'; | ||
import Manager from '../../manager'; | ||
import { HtmlManager } from '../../manager'; | ||
export interface Asset { | ||
@@ -8,3 +8,3 @@ path: string; | ||
export interface Props { | ||
manager?: Manager; | ||
manager?: HtmlManager; | ||
children: React.ReactElement<any> | string; | ||
@@ -17,5 +17,3 @@ locale?: string; | ||
bodyMarkup?: React.ReactNode; | ||
favicon?: string; | ||
title?: string; | ||
} | ||
export default function Html({ manager, children, locale, blockingScripts, scripts, styles, headMarkup, bodyMarkup, favicon, title, }: Props): JSX.Element; | ||
export default function Html({ manager, children, locale, blockingScripts, scripts, styles, headMarkup, bodyMarkup, }: 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, favicon = _a.favicon, title = _a.title; | ||
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 _h; | ||
@@ -22,4 +22,3 @@ var markup = typeof children === 'string' ? children : server_1.renderToString(children); | ||
var managedProps = (_h = {}, _h[utilities_1.MANAGED_ATTRIBUTE] = true, _h); | ||
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 titleMarkup = extracted && extracted.title ? (React.createElement("title", tslib_1.__assign({}, managedProps), extracted.title)) : null; | ||
var metaMarkup = extracted | ||
@@ -51,10 +50,8 @@ ? 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, | ||
@@ -64,9 +61,9 @@ linkMarkup, | ||
headMarkup, | ||
blockingScriptsMarkup), | ||
blockingScriptsMarkup, | ||
deferredScriptsMarkup), | ||
React.createElement("body", { style: bodyStyles }, | ||
React.createElement("div", { id: "app", dangerouslySetInnerHTML: { __html: markup } }), | ||
bodyMarkup, | ||
serializationMarkup, | ||
deferredScriptsMarkup))); | ||
serializationMarkup))); | ||
} | ||
exports.default = Html; |
{ | ||
"name": "@shopify/react-html", | ||
"version": "8.0.0-beta.1", | ||
"version": "8.0.0", | ||
"license": "MIT", | ||
@@ -26,5 +26,5 @@ "description": "A component to render your react app with no static HTML.", | ||
"dependencies": { | ||
"@shopify/react-effect": "3.0.0-beta.1", | ||
"@shopify/react-effect": "^3.0.0", | ||
"@shopify/react-serialize": "^1.0.12", | ||
"@shopify/useful-types": "^1.1.2", | ||
"@shopify/useful-types": "^1.2.1", | ||
"tslib": "^1.9.3" | ||
@@ -31,0 +31,0 @@ }, |
@@ -41,23 +41,23 @@ # `@shopify/react-html` | ||
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: | ||
If you want to make use of the serialization techniques [documented below](#in-your-app-code), you must also construct an `HtmlManager` instance, pass it to a `<HtmlContext.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>; | ||
} | ||
``` | ||
```tsx | ||
// Somewhere in your server | ||
import {extract} from '@shopify/react-effect/server'; | ||
import {render, Html, Manager} from '@shopify/react-html/server'; | ||
import { | ||
render, | ||
Html, | ||
HtmlManager, | ||
HtmlContext, | ||
} from '@shopify/react-html/server'; | ||
export default function middleware(ctx) { | ||
const manager = new Manager(); | ||
const app = <App htmlManager={manager} />; | ||
const manager = new HtmlManager(); | ||
const app = <App />; | ||
await extract(app); | ||
await extract(app, { | ||
decorate: element => ( | ||
<HtmlContext.Provider value={manager}>{element}</HtmlContext.Provider> | ||
), | ||
}); | ||
@@ -70,2 +70,19 @@ ctx.body = render(<Html manager={manager}>{app}</Html>); | ||
### In your application | ||
In order for `link`, `meta`, and `title` tags to be updated as components are mounted and unmounted, you must render the `HeadUpdater` component somewhere in your tree: | ||
```tsx | ||
// Somewhere in app code | ||
function App() { | ||
return ( | ||
<> | ||
<HeadUpdater /> | ||
<RestOfApp /> | ||
</> | ||
); | ||
} | ||
``` | ||
### In your client entrypoint | ||
@@ -206,25 +223,29 @@ | ||
### `<Link />` | ||
### `<HeadUpdater />` | ||
The `<HeadUpdater />` component is responsible for updating the head in response to `link`, `meta`, and `title` changes. You should only render one of these in your entire app. | ||
### `useLink()` and `<Link />` | ||
Renders a `<link />` tag in the head with the specified attributes. On the server, links are recorded in the `Manager` and automatically applied to the `Html` component. On the client, the `<link />` tags are updated in a deferred callback to minimize DOM manipulations. | ||
The `<Link />` component accepts any properties you would supply to a `<link />` tag. If you are using this component to create a favicon, use the [`<Favicon />`](#favicon) component instead. | ||
Both the hook and component versions accept any properties you would supply to a `<link />` tag. If you are using this component to create a favicon, use the [`useFavicon()`/ `<Favicon />` component](#favicon) instead. | ||
### `<Meta />` | ||
### `useMeta()` and `<Meta />` | ||
Renders a `<meta />` tag in the head with the specified attributes. This component uses the same approach to render these tags as detailed for the `<Link />` component above. | ||
The `<Meta />` component accepts any properties you would supply to a `<meta />` tag. | ||
Both the hook and component versions accept any properties you would supply to a `<meta />` tag. | ||
### `<Title />` | ||
### `useTitle()` and `<Title />` | ||
Renders a `<title />` tag in the head with the specified attributes. If multiple `<Title />` components are rendered in your app, the last one (usually, the most deeply nested) will be applied. | ||
Renders a `<title />` tag in the head with the specified attributes. If multiple `<Title />` components/ `useTitle()` hooks are rendered in your app, the last one (usually, the most deeply nested) will be applied. | ||
This component accepts a string child, which will be used to set the title of the page. | ||
This component accepts a string child (and the hook accepts a single string argument), which will be used to set the title of the page. | ||
### `<Favicon />` | ||
### `useFavicon()` and `<Favicon />` | ||
Renders a `<link />` tag with the necessary props to specify a favicon. Accepts a `source` property that should be the image source for the favicon. | ||
Renders a `<link />` tag with the necessary props to specify a favicon. Accepts a `source` prop that should be the image source for the favicon (the hook accepts a single string argument for the source). | ||
### `<Preconnect />` | ||
### `usePreconnect()` and `<Preconnect />` | ||
@@ -265,2 +286,2 @@ Renders a `<link />` tag that preconnects the browser to the host specified by the `source` prop. You can read more about preconnecting on [Google’s guide to resource prioritization](https://developers.google.com/web/fundamentals/performance/resource-prioritization#preconnect). | ||
- [Migrating from 4.x to 5.x](../documentation/migration-version-4-to-5.md) | ||
- [Migrating from 4.x to 5.x](./documentation/migration-version-4-to-5.md) |
export * from './components'; | ||
export {default as Manager, EFFECT_ID} from './manager'; | ||
export {HtmlContext, HtmlProvider} from './context'; | ||
export {HtmlManager, EFFECT_ID} from './manager'; | ||
export {HtmlContext} from './context'; | ||
export {showPage, getSerialized} from './utilities'; | ||
export {useDomEffect} from './hook'; | ||
export {useDomEffect} from './hooks'; | ||
export {createSerializer, useSerialized} from './serializer'; |
@@ -1,9 +0,10 @@ | ||
export {default as Favicon} from './Favicon'; | ||
export {default as Link} from './Link'; | ||
export {default as Meta} from './Meta'; | ||
export {default as Preconnect} from './Preconnect'; | ||
export {default as Script} from './Script'; | ||
export {default as Style} from './Style'; | ||
export {default as Title} from './Title'; | ||
export {default as Responsive} from './Responsive'; | ||
export {default as AppleHomeScreen} from './AppleHomeScreen'; | ||
export {Favicon} from './Favicon'; | ||
export {Link} from './Link'; | ||
export {Meta} from './Meta'; | ||
export {Preconnect} from './Preconnect'; | ||
export {Script} from './Script'; | ||
export {Style} from './Style'; | ||
export {Title} from './Title'; | ||
export {Responsive} from './Responsive'; | ||
export {AppleHomeScreen} from './AppleHomeScreen'; | ||
export {HeadUpdater} from './HeadUpdater'; |
@@ -20,3 +20,3 @@ import {EffectKind} from '@shopify/react-effect'; | ||
export default class Manager { | ||
export class HtmlManager { | ||
effect: EffectKind = { | ||
@@ -23,0 +23,0 @@ id: EFFECT_ID, |
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
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
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
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
96130
92
0
284
0
2039
+ Added@shopify/react-effect@3.3.6(transitive)
- Removed@shopify/react-effect@3.0.0-beta.1(transitive)
Updated@shopify/react-effect@^3.0.0
Updated@shopify/useful-types@^1.2.1