fix: hook splitting, naming, strict mode
This commit is contained in:
parent
87e9836cca
commit
d2fabd902d
|
@ -5,8 +5,9 @@ export const hooks = {
|
||||||
* Note: `req` will be only be defined when server-side rendering.
|
* Note: `req` will be only be defined when server-side rendering.
|
||||||
* @param {http.ClientRequest} req The HTTP request object.
|
* @param {http.ClientRequest} req The HTTP request object.
|
||||||
* @invoke SequentialAsync
|
* @invoke SequentialAsync
|
||||||
* @returns {[ReactContextProvider<Props>, Props]} An array where the first element is a React
|
* @returns {ReactContextProvider | [ReactContextProvider<Props>, Props]} A React context
|
||||||
* context provider and the second element is the `props` passed to the context provider.
|
* provider or an array where the first element is a React context provider and the second
|
||||||
|
* element is the `props` passed to the provider.
|
||||||
*/
|
*/
|
||||||
'@flecks/react.providers': (req) => {
|
'@flecks/react.providers': (req) => {
|
||||||
return req ? serverSideProvider(req) : [SomeContext.Provider, {value: 'whatever'}];
|
return req ? serverSideProvider(req) : [SomeContext.Provider, {value: 'whatever'}];
|
||||||
|
@ -19,7 +20,9 @@ export const hooks = {
|
||||||
* or an array of two elements where the first element is the component and the second element
|
* or an array of two elements where the first element is the component and the second element
|
||||||
* is the props passed to the component.
|
* is the props passed to the component.
|
||||||
* @param {http.ClientRequest} req The HTTP request object.
|
* @param {http.ClientRequest} req The HTTP request object.
|
||||||
* @invoke Async
|
* @returns {Component | [Component<Props>, Props]} A React component or an array where the first
|
||||||
|
* element is a React component and the second element is the `props` passed to the component.
|
||||||
|
* @invoke SequentialAsync
|
||||||
*/
|
*/
|
||||||
'@flecks/react.roots': (req) => {
|
'@flecks/react.roots': (req) => {
|
||||||
// Note that we're not returning `<Component />`, but `Component`.
|
// Note that we're not returning `<Component />`, but `Component`.
|
||||||
|
|
|
@ -43,8 +43,8 @@
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-refresh": "^0.14.0",
|
"react-refresh": "^0.14.0",
|
||||||
"react-router": "6.2.1",
|
"react-router": "6.22.1",
|
||||||
"react-router-dom": "6.2.1",
|
"react-router-dom": "6.22.1",
|
||||||
"react-tabs": "^6.0.2",
|
"react-tabs": "^6.0.2",
|
||||||
"redux-first-history": "5.1.1"
|
"redux-first-history": "5.1.1"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,30 +1,21 @@
|
||||||
import {D} from '@flecks/core';
|
import {D} from '@flecks/core';
|
||||||
import React from 'react';
|
|
||||||
import {createRoot, hydrateRoot} from 'react-dom/client';
|
import {createRoot, hydrateRoot} from 'react-dom/client';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
import {createRootElement} from './create-root-element';
|
||||||
import FlecksContext from '@flecks/react/context';
|
|
||||||
import root from './root';
|
|
||||||
|
|
||||||
const debug = D('@flecks/react/client');
|
const debug = D('@flecks/react/client');
|
||||||
|
|
||||||
export {FlecksContext};
|
|
||||||
|
|
||||||
export const hooks = {
|
export const hooks = {
|
||||||
'@flecks/web/client.up': async (container, flecks) => {
|
'@flecks/web/client.up': async (container, flecks) => {
|
||||||
const {ssr} = flecks.get('@flecks/react');
|
const {ssr} = flecks.get('@flecks/react');
|
||||||
debug('%sing...', ssr ? 'hydrat' : 'render');
|
debug('%sing...', ssr ? 'hydrat' : 'render');
|
||||||
const RootComponent = React.createElement(
|
const RootElement = await createRootElement(flecks);
|
||||||
React.StrictMode,
|
|
||||||
{},
|
|
||||||
[React.createElement(await root(flecks), {key: 'root'})],
|
|
||||||
);
|
|
||||||
// API™.
|
// API™.
|
||||||
if (ssr) {
|
if (ssr) {
|
||||||
hydrateRoot(container, RootComponent);
|
hydrateRoot(container, RootElement);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
createRoot(container).render(RootComponent);
|
createRoot(container).render(RootElement);
|
||||||
}
|
}
|
||||||
debug('rendered');
|
debug('rendered');
|
||||||
},
|
},
|
||||||
|
|
71
packages/react/src/create-root-element.js
Normal file
71
packages/react/src/create-root-element.js
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import {D} from '@flecks/core';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const debug = D('@flecks/react/root');
|
||||||
|
const debugSilly = debug.extend('silly');
|
||||||
|
|
||||||
|
export async function createRootElement(flecks, req, res) {
|
||||||
|
const Providers = {};
|
||||||
|
const implementingProvider = flecks.flecksImplementing('@flecks/react.providers');
|
||||||
|
for (let i = 0; i < implementingProvider.length; ++i) {
|
||||||
|
const fleck = implementingProvider[i];
|
||||||
|
const implementation = flecks.fleckImplementation(fleck, '@flecks/react.providers');
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
Providers[fleck] = await implementation(req, res, flecks);
|
||||||
|
}
|
||||||
|
debugSilly('providers: %O', Providers);
|
||||||
|
const Roots = {};
|
||||||
|
const implementingRoot = flecks.flecksImplementing('@flecks/react.roots');
|
||||||
|
for (let i = 0; i < implementingRoot.length; ++i) {
|
||||||
|
const fleck = implementingRoot[i];
|
||||||
|
const implementation = flecks.fleckImplementation(fleck, '@flecks/react.roots');
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
Roots[fleck] = await implementation(req, res, flecks);
|
||||||
|
}
|
||||||
|
debugSilly('roots: %O', Roots);
|
||||||
|
const RootElements = Object.entries(Roots)
|
||||||
|
.filter(([, ComponentOrTuple]) => ComponentOrTuple)
|
||||||
|
.map(([fleck, ComponentOrTuple]) => {
|
||||||
|
let Root;
|
||||||
|
let props = {};
|
||||||
|
if (Array.isArray(ComponentOrTuple)) {
|
||||||
|
[Root, props] = ComponentOrTuple;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Root = ComponentOrTuple;
|
||||||
|
}
|
||||||
|
return React.createElement(Root, {key: `@flecks/react/root(${fleck})`, ...props});
|
||||||
|
});
|
||||||
|
let [RootElement] = Object.entries(Providers)
|
||||||
|
.filter(([, ComponentOrTuple]) => ComponentOrTuple)
|
||||||
|
.reduceRight(
|
||||||
|
(children, [fleck, ComponentOrTuple]) => {
|
||||||
|
let Provider;
|
||||||
|
let props = {};
|
||||||
|
if (Array.isArray(ComponentOrTuple)) {
|
||||||
|
[Provider, props] = ComponentOrTuple;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Provider = ComponentOrTuple;
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
React.createElement(
|
||||||
|
Provider,
|
||||||
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
|
{key: `@flecks/react/provider(${fleck})`, ...props},
|
||||||
|
children,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
RootElements,
|
||||||
|
);
|
||||||
|
const {strictMode} = flecks.get('@flecks/react');
|
||||||
|
if (strictMode) {
|
||||||
|
RootElement = React.createElement(
|
||||||
|
React.StrictMode,
|
||||||
|
{},
|
||||||
|
[React.cloneElement(RootElement, {key: '@flecks/react.strictMode'})],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return RootElement;
|
||||||
|
}
|
|
@ -1,15 +1,12 @@
|
||||||
|
import FlecksContext from './context';
|
||||||
|
|
||||||
export {default as classnames} from 'classnames';
|
export {default as classnames} from 'classnames';
|
||||||
export {default as PropTypes} from 'prop-types';
|
export {default as PropTypes} from 'prop-types';
|
||||||
export {default as React} from 'react';
|
export {default as React} from 'react';
|
||||||
export {default as ReactDom} from 'react-dom';
|
export {default as ReactDom} from 'react-dom';
|
||||||
export * from 'react';
|
export * from 'react';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies, import/no-unresolved
|
export * from './react-hooks';
|
||||||
export {default as FlecksContext} from '@flecks/react/context';
|
|
||||||
export {default as gatherComponents} from './gather-components';
|
|
||||||
export {default as useEvent} from './hooks/use-event';
|
|
||||||
export {default as useFlecks} from './hooks/use-flecks';
|
|
||||||
export {default as usePrevious} from './hooks/use-previous';
|
|
||||||
|
|
||||||
export const hooks = {
|
export const hooks = {
|
||||||
'@flecks/core.config': () => ({
|
'@flecks/core.config': () => ({
|
||||||
|
@ -21,6 +18,11 @@ export const hooks = {
|
||||||
* Whether to enable server-side rendering.
|
* Whether to enable server-side rendering.
|
||||||
*/
|
*/
|
||||||
ssr: true,
|
ssr: true,
|
||||||
|
/**
|
||||||
|
* Whether to wrap the React root with `React.StrictMode`.
|
||||||
|
*/
|
||||||
|
strictMode: true,
|
||||||
}),
|
}),
|
||||||
|
'@flecks/react.providers': (req, res, flecks) => [FlecksContext.Provider, {value: flecks}],
|
||||||
'@flecks/web.config': async (req, flecks) => flecks.get('@flecks/react'),
|
'@flecks/web.config': async (req, flecks) => flecks.get('@flecks/react'),
|
||||||
};
|
};
|
||||||
|
|
3
packages/react/src/react-hooks/index.js
vendored
Normal file
3
packages/react/src/react-hooks/index.js
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export {default as useEvent} from './use-event';
|
||||||
|
export {default as useFlecks} from './use-flecks';
|
||||||
|
export {default as usePrevious} from './use-previous';
|
|
@ -1,5 +1,4 @@
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies, import/no-unresolved
|
import FlecksContext from '@flecks/react/context';
|
||||||
import {FlecksContext} from '@flecks/react/client';
|
|
||||||
import {useContext, useEffect, useState} from 'react';
|
import {useContext, useEffect, useState} from 'react';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
|
@ -1,37 +0,0 @@
|
||||||
import {D} from '@flecks/core';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
||||||
import FlecksContext from '@flecks/react/context';
|
|
||||||
|
|
||||||
import gatherComponents from './gather-components';
|
|
||||||
|
|
||||||
const debug = D('@flecks/react/root');
|
|
||||||
const debugSilly = debug.extend('silly');
|
|
||||||
|
|
||||||
export default async (flecks, req) => {
|
|
||||||
const Roots = await flecks.invokeAsync('@flecks/react.roots', req);
|
|
||||||
debugSilly('roots: %O', Roots);
|
|
||||||
const Providers = await flecks.invokeSequentialAsync('@flecks/react.providers', req);
|
|
||||||
const FlattenedProviders = [];
|
|
||||||
for (let i = 0; i < Providers.length; i++) {
|
|
||||||
const Provider = Providers[i];
|
|
||||||
if (Provider.length > 0) {
|
|
||||||
FlattenedProviders.push(...(Array.isArray(Provider[0]) ? Provider : [Provider]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
debugSilly('providers: %O', FlattenedProviders);
|
|
||||||
return () => {
|
|
||||||
const RootElements = [[FlecksContext.Provider, {value: flecks}]]
|
|
||||||
.concat(FlattenedProviders)
|
|
||||||
.reduceRight((children, [Provider, props], i) => [
|
|
||||||
React.createElement(
|
|
||||||
Provider,
|
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
|
||||||
{key: `@flecks/react/provider(${i})`, ...props},
|
|
||||||
children,
|
|
||||||
),
|
|
||||||
], gatherComponents(Roots));
|
|
||||||
return RootElements[0];
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,13 +0,0 @@
|
||||||
import {Flecks} from '@flecks/core';
|
|
||||||
|
|
||||||
import ssr from './ssr';
|
|
||||||
|
|
||||||
export const hooks = {
|
|
||||||
'@flecks/electron/server.extensions': (installer) => [installer.REACT_DEVELOPER_TOOLS],
|
|
||||||
'@flecks/web/server.stream.html': Flecks.priority(
|
|
||||||
(stream, req, res, flecks) => (
|
|
||||||
flecks.get('@flecks/react.ssr') ? ssr(stream, req, flecks) : stream
|
|
||||||
),
|
|
||||||
{after: '@flecks/web/server'},
|
|
||||||
),
|
|
||||||
};
|
|
|
@ -0,0 +1 @@
|
||||||
|
export const hook = (installer) => [installer.REACT_DEVELOPER_TOOLS];
|
56
packages/react/src/server/hooks/@flecks/web/_parse-html.js
Normal file
56
packages/react/src/server/hooks/@flecks/web/_parse-html.js
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import {WritableStream} from 'htmlparser2/lib/WritableStream';
|
||||||
|
|
||||||
|
export async function parseHtml(stream) {
|
||||||
|
const css = [];
|
||||||
|
let hasVendor = false;
|
||||||
|
let inline = '';
|
||||||
|
let isInScript = 0;
|
||||||
|
let isSkipping = false;
|
||||||
|
const js = [];
|
||||||
|
const parserStream = new WritableStream({
|
||||||
|
onclosetag(tagName) {
|
||||||
|
if ('script' === tagName) {
|
||||||
|
isInScript -= 1;
|
||||||
|
isSkipping = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onopentag(tagName, attribs) {
|
||||||
|
if ('script' === tagName) {
|
||||||
|
isInScript += 1;
|
||||||
|
if ('ignore' === attribs?.['data-flecks']) {
|
||||||
|
isSkipping = true;
|
||||||
|
}
|
||||||
|
else if (attribs.src) {
|
||||||
|
if (attribs.src.match(/web-vendor\.js$/)) {
|
||||||
|
hasVendor = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
js.push(attribs.src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ('style' === tagName && attribs['data-href']) {
|
||||||
|
css.push(attribs['data-href']);
|
||||||
|
}
|
||||||
|
if ('link' === tagName && 'stylesheet' === attribs.rel && attribs.href) {
|
||||||
|
css.push(attribs.href);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ontext(text) {
|
||||||
|
if (isInScript > 0 && !isSkipping) {
|
||||||
|
inline += text;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const piped = stream.pipe(parserStream);
|
||||||
|
piped.on('error', reject);
|
||||||
|
piped.on('finish', resolve);
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
css,
|
||||||
|
hasVendor,
|
||||||
|
inline,
|
||||||
|
js,
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
import {Readable} from 'stream';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {renderToPipeableStream} from 'react-dom/server';
|
||||||
|
|
||||||
|
import {Flecks} from '@flecks/core';
|
||||||
|
import {pipesink} from '@flecks/core/server';
|
||||||
|
import {configSource} from '@flecks/web/server';
|
||||||
|
import {createRootElement} from '@flecks/react/create-root-element';
|
||||||
|
|
||||||
|
import DocumentComponent from './_document';
|
||||||
|
import {parseHtml} from './_parse-html';
|
||||||
|
|
||||||
|
export const hook = Flecks.priority(
|
||||||
|
async (stream, req, res, flecks) => {
|
||||||
|
if (!flecks.get('@flecks/react.ssr')) {
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
appMountId,
|
||||||
|
base,
|
||||||
|
icon,
|
||||||
|
meta,
|
||||||
|
title,
|
||||||
|
} = flecks.get('@flecks/web');
|
||||||
|
// Fallback.
|
||||||
|
const fallback = pipesink(stream);
|
||||||
|
// Extract assets from HTML generated up to this point.
|
||||||
|
const {
|
||||||
|
css,
|
||||||
|
hasVendor,
|
||||||
|
inline,
|
||||||
|
js,
|
||||||
|
} = await parseHtml(stream);
|
||||||
|
// Render document.
|
||||||
|
const DocumentElement = React.createElement(
|
||||||
|
DocumentComponent,
|
||||||
|
{
|
||||||
|
appMountId: flecks.interpolate(appMountId),
|
||||||
|
base: flecks.interpolate(base),
|
||||||
|
config: React.createElement(
|
||||||
|
'script',
|
||||||
|
{dangerouslySetInnerHTML: {__html: await configSource(flecks, req)}},
|
||||||
|
),
|
||||||
|
css,
|
||||||
|
hasVendor,
|
||||||
|
icon,
|
||||||
|
meta,
|
||||||
|
root: await createRootElement(flecks, req, res),
|
||||||
|
title: flecks.interpolate(title),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const rendered = renderToPipeableStream(
|
||||||
|
DocumentElement,
|
||||||
|
{
|
||||||
|
bootstrapScripts: js,
|
||||||
|
bootstrapScriptContent: inline,
|
||||||
|
async onError(error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error('SSR error:', error);
|
||||||
|
resolve(Readable.from(await fallback));
|
||||||
|
},
|
||||||
|
async onShellError(error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error('SSR shell error:', error);
|
||||||
|
resolve(Readable.from(await fallback));
|
||||||
|
},
|
||||||
|
onShellReady() {
|
||||||
|
resolve(rendered);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{after: '@flecks/web/server'},
|
||||||
|
);
|
3
packages/react/src/server/index.js
Normal file
3
packages/react/src/server/index.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import {Flecks} from '@flecks/core';
|
||||||
|
|
||||||
|
export const hooks = Flecks.hooks(require.context('./hooks'));
|
|
@ -1,110 +0,0 @@
|
||||||
import {Readable} from 'stream';
|
|
||||||
|
|
||||||
import {WritableStream} from 'htmlparser2/lib/WritableStream';
|
|
||||||
import React from 'react';
|
|
||||||
import {renderToPipeableStream} from 'react-dom/server';
|
|
||||||
|
|
||||||
import {configSource} from '@flecks/web/server';
|
|
||||||
import Document from './document';
|
|
||||||
import root from './root';
|
|
||||||
|
|
||||||
export default async (stream, req, flecks) => {
|
|
||||||
const {
|
|
||||||
appMountId,
|
|
||||||
base,
|
|
||||||
icon,
|
|
||||||
meta,
|
|
||||||
title,
|
|
||||||
} = flecks.get('@flecks/web');
|
|
||||||
// Extract assets.
|
|
||||||
const css = [];
|
|
||||||
let hasVendor = false;
|
|
||||||
let inline = '';
|
|
||||||
let isInScript = 0;
|
|
||||||
let isSkipping = false;
|
|
||||||
const js = [];
|
|
||||||
const parserStream = new WritableStream({
|
|
||||||
onclosetag(tagName) {
|
|
||||||
if ('script' === tagName) {
|
|
||||||
isInScript -= 1;
|
|
||||||
isSkipping = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onopentag(tagName, attribs) {
|
|
||||||
if ('script' === tagName) {
|
|
||||||
isInScript += 1;
|
|
||||||
if ('ignore' === attribs?.['data-flecks']) {
|
|
||||||
isSkipping = true;
|
|
||||||
}
|
|
||||||
else if (attribs.src) {
|
|
||||||
if (attribs.src.match(/web-vendor\.js$/)) {
|
|
||||||
hasVendor = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
js.push(attribs.src);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ('style' === tagName && attribs['data-href']) {
|
|
||||||
css.push(attribs['data-href']);
|
|
||||||
}
|
|
||||||
if ('link' === tagName && 'stylesheet' === attribs.rel && attribs.href) {
|
|
||||||
css.push(attribs.href);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ontext(text) {
|
|
||||||
if (isInScript > 0 && !isSkipping) {
|
|
||||||
inline += text;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const chunks = [];
|
|
||||||
stream.on('data', (chunk) => {
|
|
||||||
chunks.push(chunk);
|
|
||||||
});
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
const piped = stream.pipe(parserStream);
|
|
||||||
piped.on('error', reject);
|
|
||||||
piped.on('finish', resolve);
|
|
||||||
});
|
|
||||||
// Render document.
|
|
||||||
const DocumentElement = React.createElement(
|
|
||||||
Document,
|
|
||||||
{
|
|
||||||
appMountId: flecks.interpolate(appMountId),
|
|
||||||
base: flecks.interpolate(base),
|
|
||||||
config: React.createElement(
|
|
||||||
'script',
|
|
||||||
{dangerouslySetInnerHTML: {__html: await configSource(flecks, req)}},
|
|
||||||
),
|
|
||||||
css,
|
|
||||||
hasVendor,
|
|
||||||
icon,
|
|
||||||
meta,
|
|
||||||
root: React.createElement(await root(flecks, req)),
|
|
||||||
title: flecks.interpolate(title),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const rendered = renderToPipeableStream(
|
|
||||||
DocumentElement,
|
|
||||||
{
|
|
||||||
bootstrapScripts: js,
|
|
||||||
bootstrapScriptContent: inline,
|
|
||||||
onError(error) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error('SSR error:', error);
|
|
||||||
resolve(Readable.from(Buffer.concat(chunks)));
|
|
||||||
},
|
|
||||||
onShellError(error) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error('SSR shell error:', error);
|
|
||||||
resolve(Readable.from(Buffer.concat(chunks)));
|
|
||||||
},
|
|
||||||
onShellReady() {
|
|
||||||
resolve(rendered);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
Loading…
Reference in New Issue
Block a user