WordPress / gutenberg

The Block Editor project for WordPress and beyond. Plugin is available from the official repository.
https://wordpress.org/gutenberg/
Other
10.33k stars 4.12k forks source link

Create Block: Create a more modular structure that is easier to extend to several blocks #25188

Open klinge opened 4 years ago

klinge commented 4 years ago

Is your feature request related to a problem? Please describe. I've been struggling for days to set up a plugin that implements several blocks. I've looked at examples at github and around the net but most are outdated compared to newer block standards. I admit that my skills in using javascript/webpack/npm are fairly low.

Using the @wordpress/create-block script I quickly get a plugin with a single block up and running - but it's not easily extendable to several blocks. The php side of block init+setup is in the common "plugin.php" file. And when trying move stuff that is common to a block and place that in a subfolder to the src-folder I realize that the build/webpack scripts are not really supporting that.

What solution would you like I would like the create-block script to generate a structure that is more modular and easier to extend to implementing more than a single block. Something like this:

plugin-folder
/build
/src
--/block1
----<all js, php, css-files that are unique to block1>
--/block2
----<all js, php, css-files that are unique to block2>
--/common-assets
----<files that are common between blocks - included js libraries, common styling etc>
plugin.php
package.json
..

Alternative solutions The alternative would be to create as many plugins as you want blocks but this seems a bit unnecessary if creating several blocks.

gziolo commented 3 years ago

The plan is to make it possible to build and use external templates for Create Block as proposed in https://github.com/WordPress/gutenberg/pull/23712. When it's implemented it should be much easier to share alternative configurations that go beyond the setup optimized for Block Directory (https://wordpress.org/support/article/block-directory/) publishing.

gziolo commented 3 years ago

@wordpress/create-block is one step closer to make it possible to support multiple blocks. It works now with custom templates published as npm packages. https://github.com/WordPress/gutenberg/pull/27881 is a good example that is being explored that contains a variation of the single block from the block creation tutorial. Those templates can have any number of blocks but it isn’t possible to dynamically pick number of such blocks or collect individual input for all of them. It isn’t clear how far we want to customize the scaffolding tool in the near future. How do you envision interactive mode in CLI for the proposed plugin structure?

skorasaurus commented 3 years ago

Been dealing with this on and off over the past couple months and @bosconian-dynamics has offered some solutions

https://wordpress.stackexchange.com/questions/390282/possible-to-use-wordpress-create-block-with-multiple-blocks

gziolo commented 3 years ago

It looks like it is a recurring request from the community that I see more often these days. The most recent one happened on Twitter and was started by @cr0ybot:

https://twitter.com/cr0ybot/status/1435379030522478596

Struggling with folder structure for new WordPress Gutenberg features, esp. block.json. I want one plugin that handles multiple blocks, & each block has a folder with all relevant src files. So it contains index.js, block.json, style.css, and also index.php for dynamic blocks.

This requires that we add support to @wordpress/create-block the capability to provide a CLI option to generate more than one block and based on that let users fill in details about multiple block. The other aspect is to settle on the folder structure used in the configuration with multiple blocks.

cr0ybot commented 3 years ago

To add to what @gziolo pulled in from my tweet, while I do believe that the standard create-block template should offer a best-practice approach for multiple blocks, the main thing I'm struggling with in this context is the location of the block.json file. It doesn't look like create-block currently creates a block.json in the template, but I assume it would put it in the root of the plugin following the current single-block approach.

I prefer to have all files related to each "feature" grouped together in a single folder when possible. This works fine for JS+CSS only, but when you throw block.json in the mix you suddenly need a separate folder/location for the main block metadata. The funny thing is the Gutenberg block-library package is structured the way I want: all files for a block in one folder, including the block.json file. I haven't dug in to see how those metadata files are handled on compile—are they copied to another location?

gziolo commented 3 years ago

It doesn't look like create-block currently creates a block.json in the template, but I assume it would put it in the root of the plugin following the current single-block approach.

It creates block.json in the top-level directory and it's independent from the selected template. We can definitely iterate on that when multiple blocks are supported as it won't scale 😄

I haven't dug in to see how those metadata files are handled on compile—are they copied to another location?

Yes, they are copied (with webpack plugin) to the build folder:

Screen Shot 2021-09-08 at 18 17 22
cr0ybot commented 3 years ago

~EDIT: This setup is currently not working. For some reason the renamed *.json files aren't playing nicely with register_block_type_from_metadata(). See additional comments below.~

EDIT: Code below updated to include "-block.json" at the end of the custom metadata filenames so that they are recognized by register_block_type_from_metadata(). The "-block" should not be needed after WordPress 5.9 is released, see https://core.trac.wordpress.org/ticket/53806.

I've written up a quick webpack config for my project that moves the block.json file into the build folder. My plugin looks like this:

build
src
├ blocks
│  ├ my-block
│  │  ├ block.json
│  │  ├ index.js
│  │  └ style.scss
│  └ my-other-block
└ index.js
package.json
plugin.php
webpack.config.js

webpack.config.js

const { basename, dirname } = require('path');

const CopyWebpackPlugin = require('copy-webpack-plugin');
const defaultConfig = require('@wordpress/scripts/config/webpack.config');

module.exports = {
    ...defaultConfig,
    plugins: [
        ...defaultConfig.plugins,
        new CopyWebpackPlugin({
            patterns: [
                {
                    // Copy block.json to build folder with name of block as filename
                    from: 'src/blocks/**/block.json',
                    to({ absoluteFilename }) {
                        // Get the block folder name
                        const blockName = basename(dirname(absoluteFilename));
                        // Output with original extension (.json)
                        return `./${blockName}-block[ext]`;
                    },
                },
            ],
        }),
    ],
};

This copies the block.json file into the root of /build, so the relative paths within are as simple as possible:

{
  ...
  "editorScript": "file:index.js",
  "style": "file:style-index.css"
}

EDIT: I've removed the preceding ./ from the relative paths above, they aren't needed and the client side URLs end up output that way.

In plugin.php, each metadata file is fed to register_block_type().

I do need to apologize because this is starting to feel a little off-topic from the initial purpose of this issue. Sorry about that. 😓

gziolo commented 3 years ago

@cr0ybot, thank you for sharing more details. It is very helpful to plan future iterations.

This copies the block.json file into the root of /build, so the relative paths within are as simple as possible:

{
  ...
  "editorScript": "file:./index.js",
  "style": "file:./style-index.css"
}

I agree that colocating JS, CSS, and block.json in one build's subfolder makes everything so much simpler in terms of defining paths. The only downside is that you need to use CopyWebpackPlugin to ensure it all integrates nicely.

ryanwelcher commented 3 years ago

@gziolo @cr0ybot would it make sense to have an entry point for each block? If I am understanding the code here, it seems that all of the blocks are being combined into a single JS file. So in theory, calling the register_block_type for any block that uses the index.js file will register all the blocks on the client-side? This is probably fine in most cases, but might be worth calling out.

cr0ybot commented 3 years ago

@ryanwelcher Yea sorry, I left that part out. The main /src/index.js file is importing each block folder's index.js file, which is where the actual registerBlockType() is located. ~But I don't believe that a single call to register_block_type() in PHP would pull all of them in, since as far as I understand it only registers a single block via a namespaced identifier or the path to a block.json metadata file~. So the plugin.php file is running register_block_type() on each *.json file in the build folder, which in turn registers the script/style handles automatically for enqueuing.

Here's where I've run into a weird hitch however, and I'm still determining if I've done something wrong: when I try using register_block_type() on these renamed block.json files (say, page-header.json), it isn't working. If I rename one back to block.json it works just fine? Looking at the source for register_block_type_from_metadata(), it looks like a differently-named json file should work just fine, no? I konw this isn't the place for troubleshooting but this is a huge hitch in my block.json relocating/renaming plan...

EDIT: I just realized what you meant when you specified "client side". Yes, since the main index.js file should be enqueued, even if you only registered one block server-side, I see now that they would all be registered client side (only). Sorry!

gziolo commented 3 years ago

would it make sense to have an entry point for each block?

@ryanwelcher, I would say it depends on the use case. If you have a parent block that needs some child blocks to be fully functional then I'd say bundling them together makes sense into one JS file. In that case, you probably need to manually register a script and pass the used script handle as a reference in block.json rather than relative paths as in the example above. If you have completely independent blocks, then it seems like a JS file for each block might have some benefits in case you would like to register some of those blocks conditionally for some reason. Although, we still need to add better handling for controlling JS/CSS assets in WP Admin.

Here's where I've run into a weird hitch however, and I'm still determining if I've done something wrong: when I try using register_block_type() on these renamed block.json files (say, page-header.json), it isn't working. If I rename one back to block.json it works just fine?

register_block_type() should work with a custom name for block.json when you pass the full path to the folder. Related code in WP core:

https://github.com/WordPress/wordpress-develop/blob/0bf7d5ea0dd8761ec3026a01aaa27646db8bd7a4/src/wp-includes/blocks.php#L222-L225

gziolo commented 3 years ago

Let me also share the guidelines for The WordPress.org Plugin Directory related to single vs multiple blocks:

  1. Block Plugins are separate blocks.

Block plugins are intended to be single purpose, separate, independent blocks, not bundles or compendiums of many blocks. In most cases, a Block Plugin should contain only one single top-level block. The Block Directory will not include collections of blocks that could be reasonably split up into separate, independent, blocks.

Block plugins may contain more than one block where a clearly necessary parent/child or container/content dependency exists; for example a list block that contains list item blocks.

It's worth emphasizing that from the start the intention is to drive folks to submit their blocks to the Block Directory and this is why the documentation is organized around that idea. However, knowing that the motivation from plugin authors can differ, we should make @wordpress/scripts and @wordpress/create-block flexible enough to be able to support multiple blocks that have parent/child relation but also allow a collection of loosely related blocks.

cr0ybot commented 3 years ago

@gziolo As an agency developer, my use case is that I generally want to put all of the custom blocks for a particular site into one plugin for organization, ease of development, and a minimal number of repos.

I also was starting at the code in register_block_type_from_metadata() almost all day yesterday, and looking at it with fresh eyes I think I see the issue.

$filename      = 'block.json';
$metadata_file = ( substr( $file_or_folder, -strlen( $filename ) ) !== $filename ) ?
    trailingslashit( $file_or_folder ) . $filename :
    $file_or_folder;

Walking through the code:

  1. If the $file_or_folder string does not end with "block.json"
  2. then add "/block.json" to the end of $file_or_folder
  3. else use $file_or_folder directly

Therefore, if my metadata file is page-header.json:

  1. "/path/to/metadata/page-header.json" does not end with "block.json"
  2. append "/block.json", so $metadata_file becomes "/path/to/metadata/page-header.json/block.json"

So currently, differently-named metadata files do not work, unless you happen to name your file with "block.json" at the end, i.e. "page-header-block.json". I'm going that route for now.

I went to file this on trac but it looks like it's already being tracked: https://core.trac.wordpress.org/ticket/53806. Kinda disappointed it didn't make it into the 5.8.1 release. I've updated my custom Webpack config above to account for this.

gziolo commented 3 years ago

Therefore, if my metadata file is page-header.json:

  1. "/path/to/metadata/page-header.json" does not end with "block.json"
  2. append "/block.json", so $metadata_file becomes "/path/to/metadata/page-header.json/block.json"

So currently, differently-named metadata files do not work, unless you happen to name your file with "block.json" at the end, i.e. "page-header-block.json". I'm going that route for now.

Now I don't remember if there was some rationale behind the current implementation that was incorrectly documented, or that's a bug that needs to be fixed 🤔

Thank you for sharing the Trac ticket. This behavior has been around from the start - WordPress 5.5. I need to research the initial discussion.

Edit: In the initial implementation in the Gutenberg plugin, you would always have to name the file block.json:

https://github.com/WordPress/gutenberg/blob/33451443e4f27af3c149a89ea1095887f6690291/lib/compat.php#L26-L27

One thing that comes to my mind is integrations with the Plugin Directory or WP-CLI. A good example is WP-CLI command to extract translations, it restricts processing to files that have block.json name:

https://github.com/wp-cli/i18n-command/blob/49fb6d98ebd5cbe85c87a5efc3c0877be9e358d2/src/MakePotCommand.php#L641-L642

I don't remember how the Plugin Directory detects if the plugin contains blocks, but for sure it would be simpler if the presence of block.json file in the source code would be the way to do it.

colorful-tones commented 2 years ago

I just watched @ryanwelcher Twitch for Creating a plugin with @wordpress/create-blocks that has multiple blocks, and this is a long overdue tutorial for elevating the developer experience. 👏

As an agency developer, my use case is that I generally want to put all of the custom blocks for a particular site into one plugin for organization, ease of development, and a minimal number of repos.

@cr0ybot I believe it is even likely that non-agency developers have been grappling with this as well, and I would postulate that this hinderance (single block only) is likely keeping from everyday developers from submitting their blocks ideas. I do not have the means to back that with metrics, and merely speculating (never good, I know). However, as an agency developer I've seen this as a rather big roadblock for developer experience.

gziolo commented 2 years ago

I just watched @ryanwelcher Twitch for Creating a plugin with @wordpress/create-blocks that has multiple blocks, and this is a long overdue tutorial for elevating the developer experience. 👏

I watched the stream as well. It was great to see all the options explained by @ryanwelcher 💯 The recording is now available also on YouTube: https://www.youtube.com/watch?v=lwXXckW3dT0 (on Twitch the content gets deleted after some time).

gziolo commented 2 years ago

I wanted to share my thoughts after watching the stream from @ryanwelcher. For full transparency, I discussed the same ideas with Ryan in a private chat to better organize my thoughts.

Let me emphasize first that I fully agree that we should refactor @wordpress/create-block so it is capable to support the case with multiple blocks. It's clear that there is some technical limitation that we need to remove first before we get to the finish line. Some things mentioned in the stream greatly cover that, as well as the items discussed above in the issue:

There is also a related topic related to how the build process from @wordpress/scripts works as of today. I really like that @ryanwelcher in his stream ended up with a custom build process where every block has their assets bundled separately. It aligns with the direction that the Full Site Editing project promotes. The vision is to send to users only the code that is necessary for blocks present on the page. It contrasts with many plugins where a single JS or CSS file gets serves for all blocks. At the moment, @wordpress/scripts assumes that there is only a single JS file present in the project:

https://github.com/WordPress/gutenberg/blob/17e3641efa0ca29f0d899555487d337222871c20/packages/scripts/config/webpack.config.js#L39-L50

The initial plan was to handle also src/script.js and src/view.js as entry points when present, so we could offer some common patterns for dealing with block assets. In the scenario with multiple blocks in the plugin, we would bring another level of flexibility and detect subfolders with blocks. We can be creative here, and use glob to match a general pattern like: src/**/block.json to detect blocks and assume that JS files are in the same folder.

In theory, we could achieve similar goals by changing package.json in the project by modifying wp-scripts build command in the scripts section, but it appears to be a difficult manual process. It would be beneficial to automate that for plugin authors, so they don't have to remember to update that or do what @ryanwelcher ended up doing - included the overridden config with webpack.config.js that defines entry points.

One final thing to think about is also the structure of the build folder in the case it supports multiple blocks. In the stream, you could see the following output generated:

Screen Shot 2021-10-08 at 15 41 56

As noted in https://github.com/WordPress/gutenberg/issues/25188#issuecomment-915461227 by @cr0ybot, you could include a block.json file for every block that ends up with -block.json. However, in practice, it might be more convenient to have a subfolder for every block where file names align with the structure familiar from the one block use case:

- build
  - block-one
    - block.json
    - index.asset.php 
    - index.js
    - index.css
    - style-index.css
  - block-two
    - block.json 
    - index.asset.php
    - index.js
    - index.css
    - style-index.css
cr0ybot commented 2 years ago

@gziolo I agree with your final build folder structure, which also solves the issue of having to rename each block.json file.

Something else I'd like to point out related to the relative file paths in block.json is that they can be even simpler than I mentioned in https://github.com/WordPress/gutenberg/issues/25188#issuecomment-915461227. I've edited that comment, but the gist is that the ./ before the file path is unnecessary and actually gets output in the final URL last time I checked. I feel like I've mentioned this in another issue somewhere, but it looks like the way realpath() is being used to determine the final URL is not being handled properly.

Anyways, I'd love to see a more flexible way of handling entrypoints in wp-scripts, but I get that could be a slippery slope. I've been adding a view.js file where needed within the block's source folder (and I suppose script.js, though I haven't needed one yet). I like the idea of detecting block.json files and assuming the index.js and potentially the other entrypoints I just mentioned are in the same folder.

phil-sola commented 2 years ago

Thanks for this @gziolo, really helpful and I'm glad to see this is very much in the works to make create-block suitable for multiple blocks. This morning I have got to the point where I have been considering trying to build something separate for my own requirements without all the abstraction because it is extremely time consuming trying to make the current implementation bend to your will.

In terms of build output, I am pleased with your suggestion above of having a separate folder within build for each block. You can imagine how quickly the build file will become unmanageable to navigate without it. A separate folder per block is exactly where I see this going if possible.

Again, I also agree that if there is a way to utilise some sort of glob pattern for each block created, this would certainly be preferential. Current block updating process goes something like:

  1. Update block.json & all file paths for scripts and styles
  2. Update the php file
  3. Update the webpack.config file

If each block was output to its own block folder in build directory, this would eliminate the need to rename the block.json asset paths each time and can all keep the same name (index.js, index.css, style-index.css).

There are other use cases I believe need consideration as well, for example, global styles which you may want to compile to the build folder and enqueue within the plugin for all blocks. This may be possible already but I have been hitting my head against a brick wall today trying to get this working (I am not a webpack/babel/JS build step expert). It would be horrible to have to add common styles to each individual blocks style.scss file. I would much rather have a main.scss file or similar in the root of the src folder and maybe some sort of assets folder for partials as well, similar to the WordPress setup:

I get this may not be required by everyone, but I think the scripts should be able to handle it and be aware of possibly such a setup if you want it.

I haven't had to deal with other assets yet such as icons, svgs, fonts, images etc. but this needs careful consideration as well as again. These could all go into the src/assets folder and be compiled into the build folder as well.

I hope this is helpful, if I think of anything else which may be worth considering, I will come back and add them.

gziolo commented 2 years ago

I found some time to explore better customization options for the scaffold WordPress plugin with the goal of supporting multiple blocks. You can watch the progress at https://github.com/WordPress/gutenberg/pull/37612. The first step was to allow a custom path for the block.json file, and it wasn’t that much work. The biggest challenge is to keep backward compatibility, so custom block plugin templates continue to work. The tool in the PR is already able to generate multiple block.json files if a proper config entry (new blocks field) is included in the template.

I will look next into having an optional configuration option so the tool could locate templates only related to a single block and use them for every block defined by the template.

The last step would be to offer interactive prompts in case a template for block gets provided so the user could customize every individual block separately.

gziolo commented 2 years ago

I made some good progress in #37612, and I think it has enough changes to land them before we can add full support for multiple blocks in one scaffolded WordPress plugin:

All the above combined allows to put block.json in a custom location. When the block metadata is discovered in the src folder (or subfolders) it gets copied to the build folder, which allows using simplified paths for block assets:

{
    blockTemplatesPath: join( __dirname, 'block-templates' ),
    defaultValues: {
        folderName: 'src',
        editorScript: 'file:./index.js',
        editorStyle: 'file:./index.css',
        style: 'file:./style-index.css',
    },
}

In addition to that, we can now explicitly set templates related only to the block with blockTemplatesPath. The last missing piece that I believe should be tackled separately is to enhance the CLI to support prompts for multiple blocks that would use block templates with customized settings for every block based on the user input.

I share more details in the videos:

https://user-images.githubusercontent.com/699132/147733135-614b3169-24ba-42e9-9a15-4a0800bf3c6e.mov

https://user-images.githubusercontent.com/699132/147733232-f4a08005-840a-4396-80ac-c9450c6dd76c.mov

gziolo commented 2 years ago

I also opened #37661 that enhances how entry points for the project are discovered. They now can be detected by scanning all script fields in block.json files located in the src directory. The fallback entry point remains the same src/index.js (other supported extensions: .jsx, .ts, and .tsx) in case there is no block.json file found.

This complements #37612 where the @wordpress/create-block scaffolding tool can now put block.json file in the src directory and copies those files to the build folder when running wp-scripts build. The same feature can also work separately with extra effort on the plugin author's side as explained in this issue, for example in https://github.com/WordPress/gutenberg/issues/25188#issuecomment-915461227 😄

More details in the screencast:

https://user-images.githubusercontent.com/699132/147756121-3346157e-751d-4bba-831a-3ce2d1486f93.mov

https://user-images.githubusercontent.com/699132/147761139-e36ccc58-9926-4a70-8922-f08c8d1e8878.mov

Screenshot 2021-12-30 at 15 38 16
gziolo commented 2 years ago

I landed today two PRs:

As noted in the second PR. The only case where I'm not 100% sure how changes applied to npm run build would fit into the existing projects when someone has src/index.js as an entry point but also puts block.json files in subfolders. I assumed that in such a setup you don't list file paths for JavaScript files because you would rather register a single entry point to use with all blocks and reference it as a script handle in block.json. If that's is true then the current config covers that case.

The only remaining step for resolving the issue is to revisit how the CLI prompts work to account for multiple blocks. I would appreciate some help in designing a good experience that is both flexible to support the most advanced cases but at the same time minimalistic for the most common usage.

ryanwelcher commented 2 years ago

Yesterday I live-streamed creating a custom template for the @wordpress/create-block package ( https://www.youtube.com/watch?v=AYMPsCdRjSQ around the 1hr mark ) and found a couple of things that would be great to add to the package:

I think updating the docs is the first place to start and would be a quick win. I would also love to see a tutorial in the handbook for creating a custom template. There are definitely great resources out there now but one in the handbook would probably get a lot more visibilty.

ryanwelcher commented 2 years ago

I put together #38530 which could be expanded to allow custom scripts to be defined by the template pretty easily.

gziolo commented 2 years ago

Yesterday I live-streamed creating a custom template for the @wordpress/create-block package ( https://www.twitch.tv/videos/1285533518 around the 1hr mark ) and found a couple of things that would be great to add to the package:

I had a pleasure to follow along a part of the stream and I fully agree with the list of improvements listed. Great work with both, @ryanwelcher 💯

I also see https://github.com/WordPress/gutenberg/pull/38535 now that introduces the customScripts property to allow templates to define additional scripts in package.json. I reviewed it and I think it's ready to go.

It would be amazing to either copy all files or perhaps better, define a list of files to be copied.

I completely forgot about PHP files that might be bundled with individual blocks. We copy block.json files located in the src folder, so I think it's reasonable to assume that all PHP files should be copied as well. I would at least try that first and see how far it can go.

The documentation around templatesPath and blockTemplatesPath was a little unclear as to why I would use one vs the other or if I needed both.

I immediately discovered in the stream how confusing it is. templatesPath should be now renamed to pluginTemplatesPath, but would have to internally alias to the old name for backward compatibility. The challenge still remains, because pluginTemplatesPath can contain all files related to WP plugin, but nothing prevents from including block files. blockTemplatesPath contain only template files for a single block that we might use in the future to scaffold multiple instances. If we relax the requirement to pass pluginTemplatesPath in the template, we could in theorhy put npx @wordpress/create-block into a mode where it creates a block (but no plugin wrapper) when only blockTemplatesPath is provided. It definitely needs some more thinking.

I also ran into issues because I didn't realize I needed to update a few of the default params - ie folderName was incorrect so things went into the wrong place, the editorScript, editorStyle and style defaults actually caused build errors in my setup

Yes, this part is very unfortunate. I left old values for backward compatibility:

https://github.com/WordPress/gutenberg/blob/a82af5254e60230428fbd88e1a0cba8353688e1c/packages/create-block/lib/templates.js#L203-L206

Maybe we should set the better defaults as soon as the blockTemplatesPath is provided? It's a bit tricky to document, but a better experience overall.

cr0ybot commented 2 years ago

I completely forgot about PHP files that might be bundled with individual blocks. We copy block.json files located in the src folder, so I think it's reasonable to assume that all PHP files should be copied as well.

This is how I'm doing it in my agency's (for now) custom gulp script. I admit it does feel a little weird having PHP files in the build process, but I like that all of a block's files are contained within a single folder in /build/block-name.

Also, the script/style paths in block.json become really intuitive since the files are all siblings relative to the file you're editing in src as well as when it ends up in build:

"editorScript": "file:index.js",
"editorStyle": "file:index.css",
"style": "file:style-index.css"

I purposefully drop the ./ so that the client-side URL doesn't end up with that in it; not because it wasn't working but because it just bothered me that there were unnecessary relative path parts being output.

gziolo commented 2 years ago

I purposefully drop the ./ so that the client-side URL doesn't end up with that in it; not because it wasn't working but because it just bothered me that there were unnecessary relative path parts being output.

@cr0ybot, thank you for sharing your approach. ./ should be filtered out on the server. It also causes issues with i18n as documented in https://core.trac.wordpress.org/ticket/54797. It's something that needs to be fixed as soon as possible.

ryanwelcher commented 2 years ago

With #38715 now merged, any PHP files in the src directory will be copied to the output directory automatically.

gziolo commented 2 years ago

I opened #39049 which tries to address the following issues raised by @ryanwelcher:

The documentation around templatesPath and blockTemplatesPath was a little unclear as to why I would use one vs the other or if I needed both. I also ran into issues because I didn't realize I needed to update a few of the default params - ie folderName was incorrect so things went into the wrong place, the editorScript, editorStyle and style defaults actually caused build errors in my setup

illycz commented 2 years ago

Is there official documentation or tutorial how to use create block for plugin with multiple blocks? Thanks

gziolo commented 2 years ago

Create block doesn’t support scaffolding multiple blocks but we work on it as discussed in this issue.

It’s definitely possible to use @wordpress/scripts with multiple blocks. @ryanwelcher, can you recommend one of your streams where you explain how to do it?

I hope that we finish development in the next weeks and document everything in a detailed way.

dgwyer commented 2 years ago

@illycz It's actually very easy to create multiple blocks using npx @wordpress/create-block test with a little bit of manual processing.

Once you have the single block created just add it to a folder e.g. ./src/block1 and rename all references to the block to block1. Then it's just a case of duplicating the block folder for multiple blocks and updating all block1 string instances. Don't forget to also update the PHP block registration code too.

This sounds complicated but I've done it numerous times and it's pretty simple in practice. Let me know if you run into any issues and I'll be glad to help. :)

dgwyer commented 2 years ago

@gziolo @ryanwelcher How about adding support for a --name (alias -n) CLI option to specify block names? Apologies if this has been proposed already.

Single block: npx @wordpress/create-block test -n block1 would create a single block with block1 for the block slug/name etc.

Multiple blocks: npx @wordpress/create-block test -n block1 block2 block3 would create multiple blocks, each one inside it's own sub folder ./src/[name]/ with block1, block2, block3 for the block slug/names etc.

If no -n option is specified then use the plugin slug for the block name as is the case right now.

illycz commented 2 years ago

Once you have the single block created just add it to a folder e.g. ./src/block1 and rename all references to the block to block1. Then it's just a case of duplicating the block folder for multiple blocks and updating all block1 string instances. Don't forget to also update the PHP block registration code too.

And compilation will be working within subfolders?

dgwyer commented 2 years ago

Yup, works right out of the box. wp-scripts is very flexible.

illycz commented 2 years ago

Yup, works right out of the box. wp-scripts is very flexible.

This is my actual folder hierarchy:

image

I don't want copy also node_modules which has 250MB for each block and package.json. But actual build looks to src folder in root. Is it somehow possible set build which looks to subfolders src folders?

Thanks

illycz commented 2 years ago

OK, it's possible but:

If there is better/simpler solution, I like to know about it.

Thanks

illycz commented 2 years ago

I think, the best solution would be setting glob rule for all block.json files. And in block.json set not only scripts and styles paths but also build path. Or generate build folder on same level as src.

So root-folder -- block-folder ---- src-folder within block.json, js and css files and php templates ---- build-folder with same content as src

And in packace.json something like this: "build": "wp-scripts build **/block.json" or omit path because script will find all block.json files...

But I understand that wp-scripts in not only for scaffold blocks, right?

ryanwelcher commented 2 years ago

The @wordpress/scripts package supports multiple blocks out of the box without any changes required. It just expects a certain structure is in place.

Inside of the src directory, create a directory for each block:

root
-> src
    -> block-one
        -> block.json
        -> OTHER FILES FOR YOUR BLOCK
     -> block-two
        -> block.json
        -> OTHER FILES FOR YOUR BLOCK

This structure will detect all of the block.json files and output each block in its own directory in build and copy all the appropriate files:

root
-> build
    -> block-one
       -> block.json
       -> index.asset.php
       -> index.js
       -> etc
    -> block-two
       -> block.json
       -> index.asset.php
       -> index.js
       -> etc
-> src
    -> SAME AS ABOVE

The important note here is that you need to use src as the name of the directory that contains your code ( it's the default ) in order to have the entry point detection and file copy functionality work. We have just merged https://github.com/WordPress/gutenberg/pull/39618 which will allow customization of that directory but that's not in the package yet.

illycz commented 2 years ago

Ah, OK, make sense. I will try it.

Thanks

dgwyer commented 2 years ago

You can use this utility I wrote to create multiple blocks automatically until it's available in core. It's a wrapper around the @wordpress/create-block script.

https://www.npmjs.com/package/create-wp-block

illycz commented 2 years ago

@ryanwelcher great! @dgwyer thanks :)

amjadr360 commented 2 years ago

Hi @ryanwelcher @gziolo, Thanks for all your hard work :)

I manage to handle multiple blocks. But multiple blocks do not work with --hot "Fast Refresh". It's through [HMR] Update failed: ChunkLoadError: Loading hot update chunk block-one/index failed. error.

Any idea how to solve this issue.

Thanks,

gziolo commented 2 years ago

@amjadr360, I can reproduce the same issue when using two blocks (entry points):

Screenshot 2022-04-20 at 17 58 19
gziolo commented 2 years ago

I did some investigation on the case with multiple blocks (entry points) and it's fixable but there are some technical challenges. It's also why this feature needs to be activated with --hot flag.

There is an explicit mention for the same use case in the React Fast Refresh Webpack Plugin documentation https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/docs/TROUBLESHOOTING.md#component-not-updating-with-bundle-splitting-techniques:

Only one copy of both the HMR runtime and the plugin's runtime should be embedded for one Webpack app This concern only applies when you have multiple entry points. You can use Webpack's optimization.runtimeChunk option to enforce this.

module.exports = {
  optimization: {
    runtimeChunk: 'single',
  },
};

I can confirm that adding this line to the webpack config resolves the issue but the challenge is that we now need to enqueue another script (runtime.js) in the plugin:

Screenshot 2022-04-20 at 18 13 02

I hacked it by adding the following code in my plugin before lines that register blocks:

wp_enqueue_script(
    'webpack-runtime-my-block',
    plugins_url( 'build/runtime.js', __FILE__ ),
);

However, we would need something better that could be handled automatically. I would appreciate some ideas from people on how to integrate that into the existing projects.

DeoThemes commented 2 years ago

I'm not sure if this is the right place to ask, but I'm struggling to find any information about how to include external libraries as dependencies.

Let's say I have a few blocks that rely on the Swiper slider. Swiper slider provides a way to import only specific modules, which reduces the final bundle. So if I import only specific modules inside of each block I'll end up with multiple copies of the same library in multiple blocks. If I simply copy the production bundle from /node_modules and enqueue it as a regular script, it will work, but the size of the script is very large since it includes all the modules.

I also tried to import only specific modules of the Swiper inside of my scr/js/ folder, this looks a bit hacky. The script compiles only if I provide a block.json file. But my other scripts don't see Swiper instance anymore.

root
-> src
    -> js
        -> block.json
        -> index.js // import Swiper modules here
    -> block-one
        -> block.json
        -> script.js // uses Swiper, but don't see it

in PHP I enqueue the bundle of Swiper from /build/js/index.js, but my block-one script.js doesn't see the Swiper.

What is the correct approach here?

cr0ybot commented 2 years ago

@DeoThemes The way that JavaScript is compiled with WebPack, each entrypoint and module is enclosed so that you avoid issues with everything being in the global scope. If you want the Swiper modules to be globally available on the page, you'll need to add them to the window global var and reference them that way in your various modules. That or enqueue the compiled Swiper library script (not imported modules) separately and earlier than the other scripts.

stefanfisk commented 2 years ago

@cr0ybot Webpack supports sharing libraries between entrypoints if you use optimizations.runtimeChunk = 'single'. I've used it in several projects both for WordPress and random React stuff, and it works great as long as you have tooling to figure out which files need to be loaded.

iamleese commented 2 years ago

It seems this issue has been resolved, for the most part. I was able to create multiple blocks using this method stated by @ryanwelcher.

The block registrations in the .php file just needs to point to the correct build directory.

function create_block_root_init() { register_block_type( __DIR__ . '/build/block-one' ); register_block_type( __DIR__ . '/build/block-two' ); }

The documentation for @wordpress/create-block should be updated to reflect these changes since I pretty much wasted my time trying to modify the package.json with several start/build scripts.