@shopify/react-effect
Advanced tools
Comparing version 3.0.0-beta.1 to 3.0.0
@@ -10,2 +10,17 @@ # Changelog | ||
## [3.0.0] - 2019-04-08 | ||
This library now requires React 16.8. | ||
### Added | ||
- Added a `useServerEffect` hook as an alternative to the `<Effect />` component ([#547](https://github.com/Shopify/quilt/pull/547)) | ||
## [2.1.0] | ||
### Added | ||
- Added a `maxPasses` option to `extract()` in order to limit the potential for infinite loops. This option defaults to 5 max render/ resolve cycles [#574](https://github.com/Shopify/quilt/pull/574) | ||
- All `afterEachPass`/ `betweenEachPass` callbacks now receive an argument detailing the current pass index, whether the extraction process is complete, and the duration of the render/ resolve phases [#574](https://github.com/Shopify/quilt/pull/574) | ||
## [2.0.0] | ||
@@ -12,0 +27,0 @@ |
import * as React from 'react'; | ||
import { EffectKind } from './types'; | ||
interface Options { | ||
include?: symbol[] | boolean; | ||
} | ||
export declare class EffectManager { | ||
private include; | ||
private effects; | ||
private kinds; | ||
readonly finished: boolean; | ||
constructor({ include }?: Options); | ||
add(effect: any, kind?: EffectKind): void; | ||
resolve(): Promise<void>; | ||
betweenEachPass(): void; | ||
afterEachPass(): void; | ||
shouldPerform(kind: EffectKind): boolean; | ||
} | ||
export declare const EffectContext: React.Context<EffectManager | undefined>; | ||
export {}; | ||
import { EffectManager } from './manager'; | ||
export declare const EffectContext: React.Context<EffectManager | null>; |
@@ -5,85 +5,2 @@ "use strict"; | ||
var React = tslib_1.__importStar(require("react")); | ||
var EffectManager = /** @class */ (function () { | ||
function EffectManager(_a) { | ||
var _b = (_a === void 0 ? {} : _a).include, include = _b === void 0 ? true : _b; | ||
this.effects = []; | ||
this.kinds = new Set(); | ||
this.include = include; | ||
} | ||
Object.defineProperty(EffectManager.prototype, "finished", { | ||
get: function () { | ||
return this.effects.length === 0; | ||
}, | ||
enumerable: true, | ||
configurable: true | ||
}); | ||
EffectManager.prototype.add = function (effect, kind) { | ||
if (kind != null) { | ||
this.kinds.add(kind); | ||
} | ||
if (effect == null || !('then' in effect)) { | ||
return; | ||
} | ||
this.effects.push(effect); | ||
}; | ||
EffectManager.prototype.resolve = function () { | ||
return tslib_1.__awaiter(this, void 0, void 0, function () { | ||
return tslib_1.__generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, Promise.all(this.effects)]; | ||
case 1: | ||
_a.sent(); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
}); | ||
}; | ||
EffectManager.prototype.betweenEachPass = function () { | ||
var e_1, _a; | ||
try { | ||
for (var _b = tslib_1.__values(this.kinds), _c = _b.next(); !_c.done; _c = _b.next()) { | ||
var kind = _c.value; | ||
if (typeof kind.betweenEachPass === 'function') { | ||
kind.betweenEachPass(); | ||
} | ||
} | ||
} | ||
catch (e_1_1) { e_1 = { error: e_1_1 }; } | ||
finally { | ||
try { | ||
if (_c && !_c.done && (_a = _b.return)) _a.call(_b); | ||
} | ||
finally { if (e_1) throw e_1.error; } | ||
} | ||
}; | ||
EffectManager.prototype.afterEachPass = function () { | ||
var e_2, _a; | ||
try { | ||
for (var _b = tslib_1.__values(this.kinds), _c = _b.next(); !_c.done; _c = _b.next()) { | ||
var kind = _c.value; | ||
if (typeof kind.afterEachPass === 'function') { | ||
kind.afterEachPass(); | ||
} | ||
} | ||
} | ||
catch (e_2_1) { e_2 = { error: e_2_1 }; } | ||
finally { | ||
try { | ||
if (_c && !_c.done && (_a = _b.return)) _a.call(_b); | ||
} | ||
finally { if (e_2) throw e_2.error; } | ||
} | ||
this.effects = []; | ||
this.kinds = new Set(); | ||
}; | ||
EffectManager.prototype.shouldPerform = function (kind) { | ||
var include = this.include; | ||
if (!include) { | ||
return false; | ||
} | ||
return include === true || (kind != null && include.includes(kind.id)); | ||
}; | ||
return EffectManager; | ||
}()); | ||
exports.EffectManager = EffectManager; | ||
exports.EffectContext = React.createContext(undefined); | ||
exports.EffectContext = React.createContext(null); |
@@ -6,3 +6,3 @@ import { EffectKind } from './types'; | ||
} | ||
export default function Effect({ kind, perform }: Props): null; | ||
export declare function Effect({ kind, perform }: Props): null; | ||
export {}; |
@@ -9,2 +9,2 @@ "use strict"; | ||
} | ||
exports.default = Effect; | ||
exports.Effect = Effect; |
@@ -1,5 +0,5 @@ | ||
export { default as Effect } from './Effect'; | ||
export { EffectManager } from './context'; | ||
export { Effect } from './Effect'; | ||
export { EffectManager } from './manager'; | ||
export { EffectKind } from './types'; | ||
export { useServerEffect } from './hook'; | ||
export { restrictToServer } from './utilities'; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var Effect_1 = require("./Effect"); | ||
exports.Effect = Effect_1.default; | ||
var context_1 = require("./context"); | ||
exports.EffectManager = context_1.EffectManager; | ||
exports.Effect = Effect_1.Effect; | ||
var manager_1 = require("./manager"); | ||
exports.EffectManager = manager_1.EffectManager; | ||
var hook_1 = require("./hook"); | ||
@@ -8,0 +8,0 @@ exports.useServerEffect = hook_1.useServerEffect; |
import * as React from 'react'; | ||
import { Pass } from './types'; | ||
export { Effect } from './Effect'; | ||
interface Options { | ||
include?: symbol[] | boolean; | ||
maxPasses?: number; | ||
decorate?(element: React.ReactElement<any>): React.ReactElement<any>; | ||
renderFunction?(element: React.ReactElement<{}>): string; | ||
betweenEachPass?(): any; | ||
afterEachPass?(): any; | ||
betweenEachPass?(pass: Pass): any; | ||
afterEachPass?(pass: Pass): any; | ||
} | ||
export declare function extract(app: React.ReactElement<any>, { include, decorate, renderFunction, betweenEachPass, afterEachPass, }?: Options): Promise<string>; | ||
export {}; | ||
export declare function extract(app: React.ReactElement<any>, { include, maxPasses, decorate, renderFunction, betweenEachPass, afterEachPass, }?: Options): Promise<string>; |
@@ -7,19 +7,36 @@ "use strict"; | ||
var context_1 = require("./context"); | ||
var manager_1 = require("./manager"); | ||
var Effect_1 = require("./Effect"); | ||
exports.Effect = Effect_1.Effect; | ||
var DEFAULT_MAX_PASSES = 5; | ||
function extract(app, _a) { | ||
var _b = _a === void 0 ? {} : _a, include = _b.include, _c = _b.decorate, decorate = _c === void 0 ? identity : _c, _d = _b.renderFunction, renderFunction = _d === void 0 ? server_1.renderToStaticMarkup : _d, betweenEachPass = _b.betweenEachPass, afterEachPass = _b.afterEachPass; | ||
var manager = new context_1.EffectManager({ include: include }); | ||
var _b = _a === void 0 ? {} : _a, include = _b.include, _c = _b.maxPasses, maxPasses = _c === void 0 ? DEFAULT_MAX_PASSES : _c, _d = _b.decorate, decorate = _d === void 0 ? identity : _d, _e = _b.renderFunction, renderFunction = _e === void 0 ? server_1.renderToStaticMarkup : _e, betweenEachPass = _b.betweenEachPass, afterEachPass = _b.afterEachPass; | ||
var manager = new manager_1.EffectManager({ include: include }); | ||
var element = (React.createElement(context_1.EffectContext.Provider, { value: manager }, decorate(app))); | ||
return (function perform() { | ||
return (function perform(index) { | ||
if (index === void 0) { index = 0; } | ||
return tslib_1.__awaiter(this, void 0, void 0, function () { | ||
var result; | ||
var start, result, duration, resolveStart, renderDuration, resolveDuration; | ||
return tslib_1.__generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
start = Date.now(); | ||
result = renderFunction(element); | ||
if (!manager.finished) return [3 /*break*/, 4]; | ||
return [4 /*yield*/, manager.afterEachPass()]; | ||
duration = Date.now() - start; | ||
return [4 /*yield*/, manager.afterEachPass({ | ||
index: index, | ||
finished: manager.finished, | ||
renderDuration: duration, | ||
resolveDuration: 0, | ||
})]; | ||
case 1: | ||
_a.sent(); | ||
if (!afterEachPass) return [3 /*break*/, 3]; | ||
return [4 /*yield*/, afterEachPass()]; | ||
return [4 /*yield*/, afterEachPass({ | ||
index: index, | ||
finished: manager.finished, | ||
renderDuration: duration, | ||
resolveDuration: 0, | ||
})]; | ||
case 2: | ||
@@ -29,22 +46,50 @@ _a.sent(); | ||
case 3: return [2 /*return*/, result]; | ||
case 4: return [4 /*yield*/, manager.resolve()]; | ||
case 4: | ||
resolveStart = Date.now(); | ||
renderDuration = resolveStart - start; | ||
return [4 /*yield*/, manager.resolve()]; | ||
case 5: | ||
_a.sent(); | ||
return [4 /*yield*/, manager.betweenEachPass()]; | ||
resolveDuration = Date.now() - resolveStart; | ||
return [4 /*yield*/, manager.betweenEachPass({ | ||
index: index, | ||
finished: false, | ||
renderDuration: renderDuration, | ||
resolveDuration: resolveDuration, | ||
})]; | ||
case 6: | ||
_a.sent(); | ||
if (!betweenEachPass) return [3 /*break*/, 8]; | ||
return [4 /*yield*/, betweenEachPass()]; | ||
return [4 /*yield*/, betweenEachPass({ | ||
index: index, | ||
finished: false, | ||
renderDuration: renderDuration, | ||
resolveDuration: resolveDuration, | ||
})]; | ||
case 7: | ||
_a.sent(); | ||
_a.label = 8; | ||
case 8: return [4 /*yield*/, manager.afterEachPass()]; | ||
case 8: return [4 /*yield*/, manager.afterEachPass({ | ||
index: index, | ||
finished: false, | ||
renderDuration: renderDuration, | ||
resolveDuration: resolveDuration, | ||
})]; | ||
case 9: | ||
_a.sent(); | ||
if (!afterEachPass) return [3 /*break*/, 11]; | ||
return [4 /*yield*/, afterEachPass()]; | ||
return [4 /*yield*/, afterEachPass({ | ||
index: index, | ||
finished: false, | ||
renderDuration: renderDuration, | ||
resolveDuration: resolveDuration, | ||
})]; | ||
case 10: | ||
_a.sent(); | ||
_a.label = 11; | ||
case 11: return [2 /*return*/, perform()]; | ||
case 11: | ||
if (index + 1 >= maxPasses) { | ||
return [2 /*return*/, result]; | ||
} | ||
return [2 /*return*/, perform(index + 1)]; | ||
} | ||
@@ -51,0 +96,0 @@ }); |
@@ -0,5 +1,11 @@ | ||
export interface Pass { | ||
index: number; | ||
finished: boolean; | ||
renderDuration: number; | ||
resolveDuration: number; | ||
} | ||
export interface EffectKind { | ||
readonly id: symbol; | ||
betweenEachPass?(): any; | ||
afterEachPass?(): any; | ||
betweenEachPass?(pass: Pass): any; | ||
afterEachPass?(pass: Pass): any; | ||
} |
{ | ||
"name": "@shopify/react-effect", | ||
"version": "3.0.0-beta.1", | ||
"version": "3.0.0", | ||
"license": "MIT", | ||
@@ -26,3 +26,3 @@ "description": "A component and set of utilities for performing effects within a universal React app", | ||
"dependencies": { | ||
"@shopify/useful-types": "^1.1.2", | ||
"@shopify/useful-types": "^1.2.1", | ||
"tslib": "^1.9.3" | ||
@@ -29,0 +29,0 @@ }, |
@@ -38,3 +38,3 @@ # `@shopify/react-effect` | ||
This component is now deprecated in favour of `useServerEffect`. It will removed in the next major version of `@shopify/react-effect`. | ||
This is a component version of `useServerEffect`. Its `perform` prop will be run as a server effect, and its `kind` prop is used as the second argument to `useServerEffect`. Where possible, prefer the `useServerEffect` hook. | ||
@@ -72,3 +72,3 @@ ### `extract()` | ||
// will only perform @shopify/react-i18n extraction | ||
await extract(app, [I18N_EFFECT_ID]); | ||
await extract(app, {include: [I18N_EFFECT_ID]}); | ||
ctx.body = renderToString(app); | ||
@@ -78,6 +78,8 @@ } | ||
- `betweenEachPass`: a function that is called after a pass of your tree that did not "finish" (that is, there were still promises that got collected). This function can return a promise, and it will be waited on before continuing. | ||
- `maxPasses`: a number that limits the number of render/ resolve cycles `extract` is allowed to perform. This option defaults to `5`. | ||
- `afterEachPass`: a function that is called after each pass of your tree, regardless of whether traversal is "finished". This function can return a promise, and it will be waited on before continuing. | ||
- `betweenEachPass`: a function that is called after a pass of your tree that did not "finish" (that is, there were still promises that got collected). This function can return a promise, and it will be waited on before continuing. It is called with a single argument: a `Pass` object, which contains the `index`, `finished`, `renderDuration` and `resolveDuration` of the just-completed pass. | ||
- `afterEachPass`: a function that is called after each pass of your tree, regardless of whether traversal is "finished". This function can return a promise, and it will be waited on before continuing. This function is called with the same argument as the `betweenEachPass` option. | ||
- `decorate`: a function that takes the root React element in your tree and returns a new tree to use. You can use this to wrap your application in context providers that only your server render requires. | ||
@@ -84,0 +86,0 @@ |
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
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
20905
21
298
0
148
Updated@shopify/useful-types@^1.2.1