* 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:
Mike Jolley 2020-07-14 12:35:15 +01:00 committed by GitHub
parent 1df11f5461
commit be513c8875
9 changed files with 126 additions and 47 deletions

View File

@ -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;

View File

@ -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'
)
),
} );

View File

@ -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>
);
} );
};

View File

@ -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>
);
} );
};

View File

@ -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 }.`
);
}
};

View File

@ -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 ) => () => {

View File

@ -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', {} );

View File

@ -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",

View File

@ -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' ) ),
],