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

Dependency Extraction Webpack Plugin: Add Module support #57199

Merged
merged 25 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
10 changes: 7 additions & 3 deletions packages/dependency-extraction-webpack-plugin/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@

### Breaking Changes

- Drop support for webpack 4.
- Drop support for Node.js versions < 18.
- Drop support for webpack 4.
- Drop support for Node.js versions < 18.

### New Features

- Add support for producing module-compatible asset files ([#57199](https://github.com/WordPress/gutenberg/pull/57199)).

## 4.31.0 (2023-12-13)

Expand Down Expand Up @@ -145,6 +149,6 @@

## 1.0.0 (2019-05-21)

### New Feature
### New Features

- Introduce the `@wordpress/dependency-extraction-webpack-plugin` package.
130 changes: 125 additions & 5 deletions packages/dependency-extraction-webpack-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

This webpack plugin serves two purposes:

- Externalize dependencies that are available as script dependencies on modern WordPress sites.
- Add an asset file for each entry point that declares an object with the list of WordPress script dependencies for the entry point. The asset file also contains the current version calculated for the current source code.
- Externalize dependencies that are available as shared scripts or modules on WordPress sites.
- Add an asset file for each entry point that declares an object with the list of WordPress script or module dependencies for the entry point. The asset file also contains the current version calculated for the current source code.

This allows JavaScript bundles produced by webpack to leverage WordPress style dependency sharing without an error-prone process of manually maintaining a dependency list.

Version 5 of this plugin adds support for module bundling. [Webpack's `output.module` option](https://webpack.js.org/configuration/output/#outputmodule) should
be used to opt-in to this behavior. This plugin will adapt it's behavior based on the
`output.module` option, producing an asset file suitable for use with the WordPress Module API.

Consult the [webpack website](https://webpack.js.org) for additional information on webpack concepts.

## Installation
Expand All @@ -17,7 +21,7 @@ Install the module
npm install @wordpress/dependency-extraction-webpack-plugin --save-dev
```

**Note**: This package requires Node.js 14.0.0 or later. It also requires webpack 4.8.3 and newer. It is not compatible with older versions.
**Note**: This package requires Node.js 18.0.0 or later. It also requires webpack 5.0.0 or newer. It is not compatible with older versions.

## Usage

Expand All @@ -39,7 +43,7 @@ module.exports = {

```js
const defaultConfig = require( '@wordpress/scripts/config/webpack.config' );
const config = {
const webpackConfig = {
...defaultConfig,
plugins: [
...defaultConfig.plugins.filter(
Expand All @@ -56,7 +60,9 @@ const config = {
};
```

Each entry point in the webpack bundle will include an asset file that declares the WordPress script dependencies that should be enqueued. Such file also contains the unique version hash calculated based on the file content.
### Behavior with scripts

Each entry point in the webpack bundle will include an asset file that declares the WordPress script dependencies that should be enqueued. This file also contains the unique version hash calculated based on the file content.

For example:

Expand Down Expand Up @@ -88,6 +94,68 @@ By default, the following module requests are handled:

This plugin is compatible with `externals`, but they may conflict. For example, adding `{ externals: { '@wordpress/blob': 'wp.blob' } }` to webpack configuration will effectively hide the `@wordpress/blob` module from the plugin and it will not be included in dependency lists.

### Behavior with modules

**Warning:** Modules support is considered experimental at this time.

This section describes the behavior of this package to bundle ECMAScript modules and generate asset
files suitable for use with the WordPress Modules API.

Some of this plugin's options change, and webpack requires configuration to output modules. Refer to
[webpack's documentation](https://webpack.js.org/configuration/output/#outputmodule) for up-to-date details.

```js
const webpackConfig = {
...defaultConfig,

// These lines are necessary to enable module compilation at time-of-writing:
output: { module: true },
experiments: { outputModule: true },

plugins: [
...defaultConfig.plugins.filter(
( plugin ) =>
plugin.constructor.name !== 'DependencyExtractionWebpackPlugin'
),
new DependencyExtractionWebpackPlugin( {
// With modules, we use `requestToExternalModule`:
requestToExternalModule( request ) {
if ( request === 'my-registered-module' ) {
return request;
}
},
} ),
],
};
```

Each entry point in the webpack bundle will include an asset file that declares the WordPress module dependencies that should be enqueued. This file also contains the unique version hash calculated based on the file content.

For example:

```
// Source file entrypoint.js
import { store, getContext } from '@wordpress/interactivity';

// Webpack will produce the output output/entrypoint.js
/* bundled JavaScript output */

// Webpack will also produce output/entrypoint.asset.php declaring script dependencies
<?php return array('dependencies' => array('@wordpress/interactivity'), 'version' => 'dd4c2dc50d046ed9d4c063a7ca95702f');
```

By default, the following module requests are handled:

| Request |
| ---------------------------- |
| `@wordpress/interactivity ` |

(`@wordpress/interactivity` is currently the only available WordPress module.)

**Note:** This plugin overlaps with the functionality provided by [webpack `externals`](https://webpack.js.org/configuration/externals). This plugin is intended to extract module handles from bundle compilation so that a list of module dependencies does not need to be manually maintained. If you don't need to extract a list of module dependencies, use the `externals` option directly.

This plugin is compatible with `externals`, but they may conflict. For example, adding `{ externals: { '@wordpress/blob': 'wp.blob' } }` to webpack configuration will effectively hide the `@wordpress/blob` module from the plugin and it will not be included in dependency lists.
Copy link
Member

Choose a reason for hiding this comment

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

It's more precise to say not that the plugin "overlaps" with externals, but that it actually uses the externals plugin internally! It creates a configuration for it and passes it to the externals plugin to do the actual work.

A conflict may arise when you create another instance of the externals plugin in your config, and they both want to process the same module. Then only one can win.

The other, fairly independent functionality of DEWP is finding all the external imports in the bundle and recording them in the .asset.php file.

The way how both functionalities are mixed together can be confusing for users who are trying to understand what DEWP does. We discussed that once in 2022 with @anomiex but never took any real action to fix it.

Copy link
Member Author

Choose a reason for hiding this comment

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

This part is copied from the "behavior with scripts" section. There's certainly nuance here, but externals in this case refers to externals as a configuration option (which I believe uses an ExternalPlugin under the hood, but that's not apparent via webpack config).. The goal is to make it clear that you really shouldn't be adding your own externals declarations for WordPress script/module dependencies.

If you have concrete suggestions for how to improve this (and the text in the other section) I'm happy to make changes.

Copy link
Member

Choose a reason for hiding this comment

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

If you have concrete suggestions for how to improve this

I think I'll do my own PR with a docs update, especially now when this one is merged 👍


#### Options

An object can be passed to the constructor to customize the behavior, for example:
Expand Down Expand Up @@ -142,6 +210,8 @@ Pass `useDefaults: false` to disable the default request handling.

Force `wp-polyfill` to be included in each entry point's dependency list. This would be the same as adding `import '@wordpress/polyfill';` to each entry point.

**Note**: This option is not available with modules.

##### `externalizedReport`

- Type: boolean | string
Expand All @@ -152,6 +222,8 @@ You can provide a filename, or set it to `true` to report to a default `external

##### `requestToExternal`

**Note**: This option is not available with modules. See [`requestToExternalModule`](#requestToExternalModule) for module usage.

- Type: function

`requestToExternal` allows the module handling to be customized. The function should accept a module request string and may return a string representing the global variable to use. An array of strings may be used to access globals via an object path, e.g. `wp.i18n` may be represented as `[ 'wp', 'i18n' ]`.
Expand Down Expand Up @@ -179,8 +251,43 @@ module.exports = {
};
```

##### `requestToExternalModule`

**Note**: This option is only available with modules. See [`requestToExternal`](#requestToExternal) for script usage.

- Type: function

`requestToExternalModule` allows the module handling to be customized. The function should accept a module request string and may return a string representing the module to use. Often, the module will have the same name.

`requestToExternalModule` provided via configuration has precedence over default external handling. Unhandled requests will be handled by the default unless `useDefaults` is set to `false`.

```js
/**
* Externalize 'my-module'
*
* @param {string} request Requested module
*
* @return {(string|undefined)} Script global
*/
function requestToExternalModule( request ) {
// Handle imports like `import myModule from 'my-module'`
if ( request === 'my-module' ) {
// Import should be ov the form `import { something } from "myModule";` in the final bundle.
return 'myModule';
}
}

module.exports = {
plugins: [
new DependencyExtractionWebpackPlugin( { requestToExternalModule } ),
],
};
```

##### `requestToHandle`

**Note**: This option is not available with modules. It has no corresponding module configuration.

- Type: function

All of the external modules handled by the plugin are expected to be WordPress script dependencies
Expand Down Expand Up @@ -233,6 +340,19 @@ $script_url = plugins_url( $script_path, __FILE__ );
wp_enqueue_script( 'script', $script_url, $script_asset['dependencies'], $script_asset['version'] );
```

Or with modules (the Module API is not yet stable):

```php
$module_path = 'path/to/module.js';
$module_asset_path = 'path/to/module.asset.php';
$module_asset = file_exists( $module_asset_path )
? require( $module_asset_path )
: array( 'dependencies' => array(), 'version' => filemtime( $module_path ) );
$module_url = plugins_url( $module_path, __FILE__ );
wp_register_module( 'my-module', $module_url, $module_asset['dependencies'], $module_asset['version'] );
wp_enqueue_module( 'my-module' );
```

## Contributing to this package

This is an individual package that's part of the Gutenberg project. The project is organized as a monorepo. It's made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{
// Use the default eslint parser. Prevent babel transforms.
"parser": "espree",
sirreal marked this conversation as resolved.
Show resolved Hide resolved
"env": {
"node": true
}
Expand Down
Loading
Loading