Lazy Loading Atomic Components (https://github.com/woocommerce/woocommerce-blocks/pull/2777)
* Implement lazy loading with basics spinner * Add chunkFilename * chunkFilename breaks render * Suspense at inner block level * Suspense for all products block * Update assets/js/atomic/blocks/component-init.js Co-authored-by: Darren Ethier <darren@roughsmootheng.in> * Handle lazy components in registerBlockComponent * update tests * Update locks * Update assets/js/blocks-registry/block-components/register-block-component.js Co-authored-by: Darren Ethier <darren@roughsmootheng.in> Co-authored-by: Darren Ethier <darren@roughsmootheng.in>
This commit is contained in:
parent
1df11f5461
commit
be513c8875
|
@ -2,6 +2,11 @@
|
|||
@include link-button();
|
||||
}
|
||||
|
||||
.wc-block-suspense-placeholder {
|
||||
@include placeholder();
|
||||
@include force-content();
|
||||
}
|
||||
|
||||
// These styles are for the server side rendered product grid blocks.
|
||||
.wc-block-grid__products .wc-block-grid__product-image {
|
||||
text-decoration: none;
|
||||
|
|
|
@ -2,79 +2,117 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { registerBlockComponent } from '@woocommerce/blocks-registry';
|
||||
import { lazy } from '@wordpress/element';
|
||||
import { WC_BLOCKS_BUILD_URL } from '@woocommerce/block-settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import ProductButton from './product/button/block';
|
||||
import ProductImage from './product/image/frontend';
|
||||
import ProductPrice from './product/price/block';
|
||||
import ProductRating from './product/rating/block';
|
||||
import ProductSaleBadge from './product/sale-badge/block';
|
||||
import ProductSummary from './product/summary/block';
|
||||
import ProductTitle from './product/title/frontend';
|
||||
import ProductSku from './product/sku/block';
|
||||
import ProductCategoryList from './product/category-list/block';
|
||||
import ProductTagList from './product/tag-list/block';
|
||||
import ProductStockIndicator from './product/stock-indicator/block';
|
||||
import ProductAddToCart from './product/add-to-cart/frontend';
|
||||
// Modify webpack publicPath at runtime based on location of WordPress Plugin.
|
||||
// eslint-disable-next-line no-undef,camelcase
|
||||
__webpack_public_path__ = WC_BLOCKS_BUILD_URL;
|
||||
|
||||
registerBlockComponent( {
|
||||
blockName: 'woocommerce/product-price',
|
||||
component: ProductPrice,
|
||||
component: lazy( () =>
|
||||
import(
|
||||
/* webpackChunkName: "atomic-block-components/price" */ './product/price/block'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
registerBlockComponent( {
|
||||
blockName: 'woocommerce/product-image',
|
||||
component: ProductImage,
|
||||
component: lazy( () =>
|
||||
import(
|
||||
/* webpackChunkName: "atomic-block-components/image" */ './product/image/frontend'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
registerBlockComponent( {
|
||||
blockName: 'woocommerce/product-title',
|
||||
component: ProductTitle,
|
||||
component: lazy( () =>
|
||||
import(
|
||||
/* webpackChunkName: "atomic-block-components/title" */ './product/title/frontend'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
registerBlockComponent( {
|
||||
blockName: 'woocommerce/product-rating',
|
||||
component: ProductRating,
|
||||
component: lazy( () =>
|
||||
import(
|
||||
/* webpackChunkName: "atomic-block-components/rating" */ './product/rating/block'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
registerBlockComponent( {
|
||||
blockName: 'woocommerce/product-button',
|
||||
component: ProductButton,
|
||||
component: lazy( () =>
|
||||
import(
|
||||
/* webpackChunkName: "atomic-block-components/button" */ './product/button/block'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
registerBlockComponent( {
|
||||
blockName: 'woocommerce/product-summary',
|
||||
component: ProductSummary,
|
||||
component: lazy( () =>
|
||||
import(
|
||||
/* webpackChunkName: "atomic-block-components/summary" */ './product/summary/block'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
registerBlockComponent( {
|
||||
blockName: 'woocommerce/product-sale-badge',
|
||||
component: ProductSaleBadge,
|
||||
component: lazy( () =>
|
||||
import(
|
||||
/* webpackChunkName: "atomic-block-components/sale-badge" */ './product/sale-badge/block'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
registerBlockComponent( {
|
||||
blockName: 'woocommerce/product-sku',
|
||||
component: ProductSku,
|
||||
component: lazy( () =>
|
||||
import(
|
||||
/* webpackChunkName: "atomic-block-components/sku" */ './product/sku/block'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
registerBlockComponent( {
|
||||
blockName: 'woocommerce/product-category-list',
|
||||
component: ProductCategoryList,
|
||||
component: lazy( () =>
|
||||
import(
|
||||
/* webpackChunkName: "atomic-block-components/category-list" */ './product/category-list/block'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
registerBlockComponent( {
|
||||
blockName: 'woocommerce/product-tag-list',
|
||||
component: ProductTagList,
|
||||
component: lazy( () =>
|
||||
import(
|
||||
/* webpackChunkName: "atomic-block-components/tag-list" */ './product/tag-list/block'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
registerBlockComponent( {
|
||||
blockName: 'woocommerce/product-stock-indicator',
|
||||
component: ProductStockIndicator,
|
||||
component: lazy( () =>
|
||||
import(
|
||||
/* webpackChunkName: "atomic-block-components/stock-indicator" */ './product/stock-indicator/block'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
registerBlockComponent( {
|
||||
blockName: 'woocommerce/product-add-to-cart',
|
||||
component: ProductAddToCart,
|
||||
component: lazy( () =>
|
||||
import(
|
||||
/* webpackChunkName: "atomic-block-components/add-to-cart" */ './product/add-to-cart/frontend'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { cloneElement, isValidElement } from '@wordpress/element';
|
||||
import { Suspense, cloneElement, isValidElement } from '@wordpress/element';
|
||||
import parse from 'html-react-parser';
|
||||
|
||||
/**
|
||||
|
@ -25,10 +25,7 @@ export const renderInnerBlocks = ( {
|
|||
const blockMap = getBlockMap( parentBlockName );
|
||||
|
||||
return Array.from( children ).map( ( el, index ) => {
|
||||
const componentProps = {
|
||||
...el.dataset,
|
||||
key: `${ parentBlockName }_${ depth }_${ index }`,
|
||||
};
|
||||
const componentProps = el.dataset;
|
||||
|
||||
const componentChildren =
|
||||
el.children && el.children.length
|
||||
|
@ -56,10 +53,14 @@ export const renderInnerBlocks = ( {
|
|||
}
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<LayoutComponent { ...componentProps }>
|
||||
{ componentChildren }
|
||||
</LayoutComponent>
|
||||
<Suspense
|
||||
key={ `${ parentBlockName }_${ depth }_${ index }` }
|
||||
fallback={ <div className="wc-block-placeholder" /> }
|
||||
>
|
||||
<LayoutComponent { ...componentProps }>
|
||||
{ componentChildren }
|
||||
</LayoutComponent>
|
||||
</Suspense>
|
||||
);
|
||||
} );
|
||||
};
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { getBlockMap } from '@woocommerce/atomic-utils';
|
||||
import { Suspense } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Maps a layout config into atomic components.
|
||||
|
@ -45,12 +46,16 @@ export const renderProductLayout = (
|
|||
const keyParts = [ 'layout', name, index, componentId, productID ];
|
||||
|
||||
return (
|
||||
<LayoutComponent
|
||||
<Suspense
|
||||
key={ keyParts.join( '_' ) }
|
||||
{ ...props }
|
||||
children={ children }
|
||||
product={ product }
|
||||
/>
|
||||
fallback={ <div className="wc-block-placeholder" /> }
|
||||
>
|
||||
<LayoutComponent
|
||||
{ ...props }
|
||||
children={ children }
|
||||
product={ product }
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
} );
|
||||
};
|
||||
|
|
|
@ -19,7 +19,8 @@ import { registeredBlockComponents } from './registered-block-components-init';
|
|||
*
|
||||
* @export
|
||||
* @param {Object} options Options to use when registering the block.
|
||||
* @param {Function} options.component React component that will be rendered.
|
||||
* @param {Function} options.component React component that will be rendered, or the return value from React.lazy if
|
||||
* dynamically imported.
|
||||
* @param {string} options.blockName Name of the block that this component belongs to.
|
||||
* @param {string} [options.context] To make this component available only under a certain context
|
||||
* (named parent Block) define it here. If left blank, the
|
||||
|
@ -31,7 +32,7 @@ export function registerBlockComponent( options ) {
|
|||
}
|
||||
assertOption( options, 'context', 'string' );
|
||||
assertOption( options, 'blockName', 'string' );
|
||||
assertOption( options, 'component', 'function' );
|
||||
assertBlockComponent( options, 'component' );
|
||||
|
||||
const { context, blockName, component } = options;
|
||||
|
||||
|
@ -42,6 +43,30 @@ export function registerBlockComponent( options ) {
|
|||
registeredBlockComponents[ context ][ blockName ] = component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that an option is a valid react element or lazy callback. Otherwise, throws an error.
|
||||
*
|
||||
* @throws Will throw an error if the type of the option doesn't match the expected type.
|
||||
* @param {Object} options Object containing the option to validate.
|
||||
* @param {string} optionName Name of the option to validate.
|
||||
*/
|
||||
const assertBlockComponent = ( options, optionName ) => {
|
||||
if ( options[ optionName ] ) {
|
||||
if ( typeof options[ optionName ] === 'function' ) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
options[ optionName ].$$typeof &&
|
||||
options[ optionName ].$$typeof === Symbol.for( 'react.lazy' )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
`Incorrect value for the ${ optionName } argument when registering a block component. Component must be a valid React Element or Lazy callback.`
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Asserts that an option is of the given type. Otherwise, throws an error.
|
||||
*
|
||||
|
@ -51,9 +76,10 @@ export function registerBlockComponent( options ) {
|
|||
* @param {string} expectedType Type expected for the option.
|
||||
*/
|
||||
const assertOption = ( options, optionName, expectedType ) => {
|
||||
if ( typeof options[ optionName ] !== expectedType ) {
|
||||
const actualType = typeof options[ optionName ];
|
||||
if ( actualType !== expectedType ) {
|
||||
throw new Error(
|
||||
`Incorrect value for the ${ optionName } argument when registering an inner block. It must be a ${ expectedType }.`
|
||||
`Incorrect value for the ${ optionName } argument when registering a block component. It was a ${ actualType }, but must be a ${ expectedType }.`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -11,7 +11,9 @@ import {
|
|||
describe( 'blocks registry', () => {
|
||||
const context = '@woocommerce/all-products';
|
||||
const blockName = '@woocommerce-extension/price-level';
|
||||
const component = () => {};
|
||||
const component = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
describe( 'registerBlockComponent', () => {
|
||||
const invokeTest = ( args ) => () => {
|
||||
|
|
|
@ -54,6 +54,7 @@ export const WOOCOMMERCE_BLOCKS_PHASE = getSetting(
|
|||
1
|
||||
);
|
||||
export const WC_BLOCKS_ASSET_URL = getSetting( 'wcBlocksAssetUrl', '' );
|
||||
export const WC_BLOCKS_BUILD_URL = getSetting( 'wcBlocksBuildUrl', '' );
|
||||
export const SHIPPING_COUNTRIES = getSetting( 'shippingCountries', {} );
|
||||
export const ALLOWED_COUNTRIES = getSetting( 'allowedCountries', {} );
|
||||
export const SHIPPING_STATES = getSetting( 'shippingStates', {} );
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
"phpunit": "docker-compose up -d db && docker-compose up -d --build wordpress-unit-tests && docker exec -it --workdir /var/www/html/wp-content/plugins/woocommerce-gutenberg-products-block wordpress_test php ./vendor/bin/phpunit",
|
||||
"reformat-files": "prettier --ignore-path .eslintignore --write \"**/*.{js,jsx,json,ts,tsx}\"",
|
||||
"release": "sh ./bin/wordpress-deploy.sh",
|
||||
"start": "cross-env BABEL_ENV=default webpack --watch --info-verbosity none",
|
||||
"start": "rimraf build/* && cross-env BABEL_ENV=default webpack --watch --info-verbosity none",
|
||||
"storybook": "start-storybook -c ./storybook -p 6006 --ci",
|
||||
"storybook:build": "build-storybook -c ./storybook -o ./storybook/dist",
|
||||
"test": "wp-scripts test-unit-js --config tests/js/jest.config.json",
|
||||
|
|
|
@ -169,6 +169,7 @@ class Assets {
|
|||
'isShippingCalculatorEnabled' => filter_var( get_option( 'woocommerce_enable_shipping_calc' ), FILTER_VALIDATE_BOOLEAN ),
|
||||
'isShippingCostHidden' => filter_var( get_option( 'woocommerce_shipping_cost_requires_address' ), FILTER_VALIDATE_BOOLEAN ),
|
||||
'wcBlocksAssetUrl' => plugins_url( 'assets/', __DIR__ ),
|
||||
'wcBlocksBuildUrl' => plugins_url( 'build/', __DIR__ ),
|
||||
'restApiRoutes' => [
|
||||
'/wc/store' => array_keys( Package::container()->get( RestApi::class )->get_routes_from_namespace( 'wc/store' ) ),
|
||||
],
|
||||
|
|
Loading…
Reference in New Issue