2

I have a plugin that registers four different Gutenberg blocks. I build them using the build script from the wp-scripts package. As you all surely know, this script automatically generates an index.asset.php file with an array with all the script dependencies for each block. For example:

<?php return array('dependencies' => array('react', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => 'a955a4059265b7ad1ce6');

I need to add the masonry script (included in WP) to this dependency array in just ONE (not ALL) of the blocks, like this:

<?php return array('dependencies' => array('react', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-element', 'wp-i18n', 'wp-primitives', 'masonry'), 'version' => 'a955a4059265b7ad1ce6');

I know that I can add this dependency manually after I build the blocks, but I'm wondering if there's a way to do this automatically via the block block.json file, a custom webpack.config.js file or anything else.

The contents of the block.json file is:

{
    "$schema": "https://schemas.wp.org/trunk/block.json",
    "apiVersion": 3,
    "name": "my/masonry-block",
    "title": "My Masonry Block",
    "description": "Display a Masonry gallery.",
    "textdomain": "my-domain",
    "category": "media",
    "attributes": {
        "items": {
            "type": "array",
            "default": []
        },
        "columns": {
            "type": "number",
            "minimum": 1,
            "maximum": 8,
            "default": 3
        },
        "gap": {
            "type": "string",
            "default": "10px"
        },
        "captions": {
            "type": "boolean",
            "default": false
        }
    },
    "supports": {
        "spacing": {
            "margin": true,
            "padding": true
        },
        "align": [ "center" ]
    },
    "editorScript": [
        "file:./index.js",
        "imagesloaded",
        "masonry"
    ],
    "viewScript": [
        "file:./view.js",
        "imagesloaded",
        "masonry"
    ],
    "style": [
        "file:./style-index.css",
        "file:./view.css"
    ],
    "editorStyle": "file:./index.css"
}

I'm registering the block with:

register_block_type(
    __DIR__ . '/build/blocks/masonry',
    array(
        'render_callback' => array( $this, 'render_block_masonry' ),
    )
);

This is the block edit function (some code omitted for brevity):

const displayItems = (items) => {
    return (
        items.map((item, index) => {
            return (
                <div className="gallery-item" key={index}>
                    <figure>
                        <a data-rel="collection" href={item.url} data-sub-html={item.caption}>
                            <img className={`wp-image-${item.id}`} src={item.url} alt={item.alt} key={item.id} />
                        </a>
                        {captions && item.caption &&
                            <figcaption className="wp-element-caption">
                                {item.caption}
                            </figcaption>
                        }
                    </figure>
                </div>
            )
        })
    )
};

const containerMasonryRef = useRefEffect((element) => {
    var msnry;

    const { ownerDocument } = element;
    const { defaultView } = ownerDocument;

    if (!defaultView.imagesLoaded || !defaultView.Masonry) {
        console.log('scripts not loaded');
        return;
    }
    
    imagesLoaded(element, function () {
        console.log('images loaded');
        msnry = new defaultView.Masonry(element, {
            itemSelector: '.gallery-item',
            columnWidth: '.grid-sizer',
            percentPosition: true,
            gutter: parseInt(gap),
        });
    });

    return () => {
        msnry?.destroy();
    }

}, [items, columns, gap, captions]);

const containerLightboxRef = useRefEffect((element) => {
    lightGallery(element, {
        selector: 'a[data-rel^=collection]',
        mode: 'lg-fade',
        preload: 4,
        counter: false,
        download: false,
        youtubePlayerParams: {
            autoplay: 0
        },
        vimeoPlayerParams: {
            autoplay: 0
        }
    });

    return () => {
        window?.lgData[element?.getAttribute('lg-uid')]?.destroy(true);
    }

}, []);

const mergedRefs = useMergeRefs([
    containerMasonryRef,
    containerLightboxRef,
]);

return (
    <>
        <InspectorControls>
            <PanelBody
                title={__('Settings')}
            >
                <RangeControl
                    label={__('Columns')}
                    value={columns}
                    onChange={setColumns}
                    min={1}
                    max={8}
                />
                <UnitControl
                    label={__('Gap between items')}
                    min="0"
                    onChange={setGap}
                    value={gap}
                    units={units}
                />
            </PanelBody>
        </InspectorControls>
        <BlockControls group="block">
            <ToolbarButton
                onClick={toggleCaptions}
                icon={captionIcon}
                isPressed={captions}
                label={
                    captions
                        ? __('Hide captions')
                        : __('Show captions')
                }
            />
        </BlockControls>
        <BlockControls group="other">
            {items.length > 0 && (
                <ToolbarGroup>
                    <MediaUploadCheck>
                        <MediaUpload
                            allowedTypes={['image']}
                            multiple={true}
                            gallery={true}
                            value={items.map((item) => item.id)}
                            onSelect={setItems}
                            render={({ open }) => (
                                <ToolbarButton onClick={open}>
                                    {__('Edit items')}
                                </ToolbarButton>)}
                        />
                    </MediaUploadCheck>
                </ToolbarGroup>
            )}
        </BlockControls>
        <MediaUploadCheck>
            {items.length > 0 ?
                <div {...blockProps}>
                    <div className="gallery-items" style={{ '--gap': gap }} ref={mergedRefs}>
                        <div className="grid-sizer"></div>
                        {displayItems(items)}
                    </div>
                </div>
                :
                <MediaPlaceholder
                    accept="image/*"
                    allowedTypes={['image']}
                    onSelect={setItems}
                    multiple={true}
                    gallery={true}
                    addToGallery={true}
                    handleUpload={true}
                    labels={
                        { title: __('My Masonry Block') }
                    }
                />
            }
        </MediaUploadCheck>
    </>
);

1 Answer 1

1

Actually, you do not need to worry about adding the Masonry's script handle to the dependencies array in the asset file (index.asset.php).

All you needed to do to make your Masonry code work properly is by using the script property to add the masonry (and imagesloaded) as dependencies for your editor and view/front-end scripts.

"editorScript": "file:./index.js",
"script": [
    "imagesloaded",
    "masonry"
],
"viewScript": "file:./view.js",

I don't know and haven't checked further why adding masonry to the editorScript and viewScript did not work, but the above (trick) worked well for me.


For completeness though, I created a simple Webpack plugin which basically modifies the asset source before it is written to the asset file, but (hopefully) after the Dependency Extraction Webpack Plugin added the asset to the compilation queue.

Here's the plugin which I placed it in the root project folder.

And here's my custom Webpack config file, i.e. webpack.config.js.

BTW, thanks for sharing about the Gutenberg Test Iframed Masonry Block. 🙂 Happy coding!


Update 21 Feb 2024

Regarding the strikethrough text above:

12
  • I posted the contents of my block.json file. The masonry script handle is already in there. Commented Feb 13, 2024 at 9:34
  • 1
    Sorry, I've checked the source code and it seems that the Masonry script is loaded BUT, outside of the iFramed template editor in Gutenberg. I based my code in the following example: github.com/WordPress/gutenberg/blob/… According to a core developer, this is the way to load the Masonry script INSIDE the iFramed editor, but it's not working in my case. Commented Feb 13, 2024 at 13:39
  • 1
    I believe it does need to be loaded inside the iFrame. But anyway, see the revised answer. Commented Feb 14, 2024 at 7:24
  • 1
    Thanks for your revised answer. Adding masonry and imagesloaded to the script prop instead of editorScript makes the whole thing work as expected, and both scripts are now loaded INSIDE the iframe. It's puzzling why this happens. I remember something similar happening with another block I was working on months ago, where a script needed to be loaded in the script prop to make it work in the editor. This might be a bug on the Gutenberg side because it makes no sense. Commented Feb 20, 2024 at 10:45
  • 1
    I revised my answer, yes, again! Also, I +1 your question because maybe you're right about that being a bug in WordPress/Gutenberg core. But if it wasn't, then this is truly puzzling! Commented Feb 21, 2024 at 5:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.