Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create frontend entry points for block-library, outputting code loadable from the browser. #30341

Merged
merged 1 commit into from
Apr 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions packages/block-library/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,62 @@ npm install @wordpress/block-library --save

_This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._

## Building JavaScript for the browser

If a `frontend.js` file is present in the block's directory, this file will be built along other assets, making it available to load from the browser.

This enables us to, for instance, load this file when the block is present on the page in two ways:
vcanales marked this conversation as resolved.
Show resolved Hide resolved

1. Using the block's `render_callback`:

```php
function render_my_block() {
$script_path = __DIR__ . '/block-name/frontend.js';

if ( file_exists( $script_path ) ) {
wp_enqueue_script(
'my_block_frontend_script',
plugins_url( 'frontend.js', $script_path ),
array(),
false,
true
);
}
}

function register_block_my_block() {
register_block_type_from_metadata(
__DIR__ . '/block-name',
array(
'render_callback' => 'render_my_block',
)
);
}


add_action( 'init', 'register_block_my_block' );
```

2. Using the `render_block` filter:

```php
function render_my_block() {
$script_path = __DIR__ . '/block-name/frontend.js';

if ( file_exists( $script_path ) ) {
wp_enqueue_script(
'my_block_frontend_script',
plugins_url( 'frontend.js', $script_path ),
array(),
false,
true
);
}
}

apply_filter( 'render_block', 'render_my_block' );
```

## API

<!-- START TOKEN(Autogenerated API docs) -->
Expand Down
101 changes: 61 additions & 40 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ const { DefinePlugin } = require( 'webpack' );
const CopyWebpackPlugin = require( 'copy-webpack-plugin' );
const TerserPlugin = require( 'terser-webpack-plugin' );
const postcss = require( 'postcss' );
const { get, escapeRegExp, compact } = require( 'lodash' );
const { basename, sep } = require( 'path' );
const { escapeRegExp, compact } = require( 'lodash' );
const { sep } = require( 'path' );
const fastGlob = require( 'fast-glob' );

/**
* WordPress dependencies
Expand Down Expand Up @@ -64,6 +65,35 @@ const stylesTransform = ( content ) => {
return content;
};

const blockNameRegex = new RegExp( /(?<=src\/).*(?=(\/frontend))/g );

const createEntrypoints = () => {
const scriptPaths = fastGlob.sync(
'./packages/block-library/src/**/frontend.js',
{
ignore: [ '**/build*/**' ],
Copy link
Member

@gziolo gziolo Apr 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was it necessary? There are no build folders inside the block-library/src/ folder or do I miss something?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was necessary at some point, when instead of going into /src, I would start from block-library/, removing in #30629

}
);

const scriptEntries = scriptPaths.reduce( ( entries, scriptPath ) => {
const [ blockName ] = scriptPath.match( blockNameRegex ) || [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What will happen when the block name is undefined?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fast-glob will look for frontend.js files is within block source directories, so the value of scriptPaths will always be either an empty array, or a list of paths in the form './packages/block-library/src/<blockName>/frontend.js', in which case /(?<=src\/).*(?=(\/frontend))/g would always have a match.

Because of this, the guard I set there is unnecessary.

In order to make sure, I ran npm run build, without the guard present, under the following conditions:

  • no frontend.js files present.
  • frontend.js file present in a subdirectory of a block's source directory, in which case the file gets built into that same subdirectory in the block's build directory
  • frontend.js present in multiple block directories.

I think the best think to do here would be deleting the misleading guard.
I've done this and also added more comments going through the logic in #30629

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, makes total sense, this [] was confusing 😄


return {
...entries,
[ blockName ]: scriptPath,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the blockName as the key may cause interesting issues in the future, if a block has the same name as a package, since packageEntries and scriptEntries are merged below, any duplicate named entries in scriptEntries will override the entry in packageEntries.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. At the moment we have a single script entry so it isn't an issue but with the number of packages and blocks that exist, it's possible that they could use the same name. It should be further investigated.

};
}, {} );

const packageEntries = gutenbergPackages.reduce( ( memo, packageName ) => {
return {
...memo,
[ packageName ]: `./packages/${ packageName }`,
};
}, {} );

return { ...packageEntries, ...scriptEntries };
};

module.exports = {
optimization: {
// Only concatenate modules in production, when not analyzing bundles.
Expand All @@ -90,16 +120,26 @@ module.exports = {
],
},
mode,
entry: gutenbergPackages.reduce( ( memo, packageName ) => {
const name = camelCaseDash( packageName );
memo[ name ] = `./packages/${ packageName }`;
return memo;
}, {} ),
entry: createEntrypoints(),
output: {
devtoolNamespace: 'wp',
filename: './build/[basename]/index.js',
filename: ( data ) => {
const { chunk } = data;
const { entryModule } = chunk;
const { rawRequest } = entryModule;

/*
* If the file being built is a Core Block's frontend file,
* we build it in the block's directory.
*/
if ( rawRequest && rawRequest.includes( '/frontend.js' ) ) {
return `./build/block-library/blocks/[name]/frontend.js`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's nice, it's going to be located next to block.json in the build folder so it should be a simple reference once we introduce frontend field in block.json. I like it 👍🏻

}

return './build/[name]/index.js';
},
path: __dirname,
library: [ 'wp', '[name]' ],
library: [ 'wp', '[camelName]' ],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more thing that needs more thinking. I don't think we want to produce wp globals for frontend scripts. Taking into account the comment from @pento https://github.com/WordPress/gutenberg/pull/30341/files#r614557004, it might be worth exploring whether to use two independent webpack configs to gain more flexibility in how those two different use cases are handled. We maintain multiple (2) webpack configs in WordPress core:
https://github.com/WordPress/wordpress-develop/blob/40728234568b07d6bcaa456816bf284f8c5cc670/webpack.config.js#L13-L16

libraryTarget: 'window',
},
module: {
Expand Down Expand Up @@ -135,39 +175,20 @@ module.exports = {
),
} ),
new CustomTemplatedPathPlugin( {
basename( path, data ) {
let rawRequest;

const entryModule = get( data, [ 'chunk', 'entryModule' ], {} );
switch ( entryModule.type ) {
case 'javascript/auto':
rawRequest = entryModule.rawRequest;
break;

case 'javascript/esm':
rawRequest = entryModule.rootModule.rawRequest;
break;
}

if ( rawRequest ) {
return basename( rawRequest );
}

return path;
camelName( path, data ) {
return camelCaseDash( data.chunk.name );
},
} ),
new LibraryExportDefaultPlugin(
[
'api-fetch',
'deprecated',
'dom-ready',
'redux-routine',
'token-list',
'server-side-render',
'shortcode',
'warning',
].map( camelCaseDash )
),
new LibraryExportDefaultPlugin( [
'api-fetch',
'deprecated',
'dom-ready',
'redux-routine',
'token-list',
'server-side-render',
'shortcode',
'warning',
] ),
new CopyWebpackPlugin(
gutenbergPackages.map( ( packageName ) => ( {
from: `./packages/${ packageName }/build-style/*.css`,
Expand Down