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

Feature: Add webpack plugin to externalize and extract script dependencies #14869

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
bd55123
Add package
sirreal Apr 5, 2019
7dfdebb
Add docs manifest
sirreal Apr 5, 2019
28437e8
Replace hard-coded externals with webpack plugin
sirreal Apr 8, 2019
5112eec
Extract default functions
sirreal Apr 9, 2019
8e985d9
Prepare options
sirreal Apr 9, 2019
6f6edc5
Handle useDefaults for externals
sirreal Apr 9, 2019
a2ef110
Handle custom requestToExternal function
sirreal Apr 9, 2019
95fd99d
Handle custom dependency functions
sirreal Apr 9, 2019
44a2d9b
Save dev dependency in README
sirreal Apr 9, 2019
e87bf29
Use prose package name as README title
sirreal Apr 9, 2019
cc0f892
Use ^ version ranges
sirreal Apr 9, 2019
4511cdc
Update return description
sirreal Apr 9, 2019
e6fa59f
Invert yoda conditional
nerrad Apr 9, 2019
3bc6538
Use boolean value directly
sirreal Apr 9, 2019
de9ef01
Implement injectPolyfill
sirreal Apr 9, 2019
7bf6e4a
Restore regeneratorRuntime external
sirreal Apr 10, 2019
dbe3919
Extern lodash-es
sirreal Apr 10, 2019
3c7472c
Improve clarity
sirreal Apr 10, 2019
9431de7
Handle @babel/runtime/regenerator
sirreal Apr 10, 2019
f539391
Copy camelCaseDash from scripts
sirreal Apr 11, 2019
0c40e1e
Add tests
sirreal Apr 12, 2019
dd4575d
Add unit tests and restructure to facilitate
sirreal Apr 12, 2019
9baf3dd
Test external modules and dependencies file snapshot
sirreal Apr 15, 2019
dc2daa4
Align adjacent `=` in README
sirreal Apr 15, 2019
0818e67
Add options documentation to the README
sirreal Apr 15, 2019
b233f19
Test externals conflict
sirreal Apr 15, 2019
ed7106e
Add a note about conflicts with externals
sirreal Apr 15, 2019
46ed9f9
Add test case for requires
sirreal Apr 15, 2019
8671c12
Add changelog for new package
sirreal Apr 15, 2019
88e8a18
Add @wordpress/scripts changelog and bump version
sirreal Apr 15, 2019
561c201
Remove describe wrapper and simplify test name
sirreal Apr 15, 2019
114cd78
Try: add package to root devDeps
sirreal Apr 16, 2019
210fb75
Update @wordpress/scripts README
sirreal Apr 16, 2019
7d176b1
Add bundling documentation to scripts
sirreal Apr 16, 2019
354890c
Add rimraf before tests
sirreal Apr 16, 2019
16d9479
Revert @wordpress/scripts version bump
sirreal Apr 16, 2019
82dce50
Use alpha version suffix
sirreal Apr 16, 2019
d1c5015
Use "unreleased" versions in changelogs
gziolo Apr 16, 2019
6a1177d
Use the plugin explicitly
sirreal Apr 16, 2019
30dc232
Use describe.each over explicit loop
sirreal Apr 16, 2019
172ab07
Use before/afterEach, add comment about cleanup
sirreal Apr 16, 2019
ce0053b
Update README href
gziolo Apr 16, 2019
bab86af
Add externals info to scripts README
sirreal Apr 16, 2019
e801600
Add default module request handling table to README
sirreal Apr 16, 2019
70a89a4
Use this externalType and array object path
sirreal Apr 16, 2019
30aa120
Update tests to use array paths
sirreal Apr 16, 2019
705e4e3
Add dynamic-import test
sirreal Apr 16, 2019
263e409
Update package-lock
sirreal Apr 8, 2019
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
12 changes: 12 additions & 0 deletions docs/contributors/scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,15 @@ It is recommended to use the main `wp-polyfill` script handle which takes care o
| [Formdata Polyfill](https://www.npmjs.com/package/formdata-polyfill) | wp-polyfill-formdata| Polyfill conditionally replaces the native implementation |
| [Node Contains Polyfill](https://polyfill.io) | wp-polyfill-node-contains |Polyfill for Node.contains |
| [Element Closest Polyfill](https://www.npmjs.com/package/element-closest) | wp-polyfill-element-closest| Return the closest element matching a selector up the DOM tree |

## Bundling and code sharing

When using a JavaScript bundler like [webpack](https://webpack.js.org/), the scripts mentioned here
can be excluded from the bundle and provided by WordPress in the form of script dependencies [(see
`wp_enqueue_script`)][https://developer.wordpress.org/reference/functions/wp_enqueue_script/#default-scripts-included-and-registered-by-wordpress].

The
[`@wordpress/dependency-extraction-webpack-plugin`](https://github.com/WordPress/gutenberg/tree/master/packages/dependency-extraction-webpack-plugin)
provides a webpack plugin to help extract WordPress dependencies from bundles. `@wordpress/scripts`
[`build`](https://github.com/WordPress/gutenberg/tree/master/packages/scripts#build) script includes
the plugin by default.
6 changes: 6 additions & 0 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,12 @@
"markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/date/README.md",
"parent": "packages"
},
{
"title": "@wordpress/dependency-extraction-webpack-plugin",
"slug": "packages-dependency-extraction-webpack-plugin",
"markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/dependency-extraction-webpack-plugin/README.md",
"parent": "packages"
},
{
"title": "@wordpress/deprecated",
"slug": "packages-deprecated",
Expand Down
27 changes: 27 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"@wordpress/babel-preset-default": "file:packages/babel-preset-default",
"@wordpress/browserslist-config": "file:packages/browserslist-config",
"@wordpress/custom-templated-path-webpack-plugin": "file:packages/custom-templated-path-webpack-plugin",
"@wordpress/dependency-extraction-webpack-plugin": "file:packages/dependency-extraction-webpack-plugin",
"@wordpress/docgen": "file:packages/docgen",
"@wordpress/e2e-test-utils": "file:packages/e2e-test-utils",
"@wordpress/e2e-tests": "file:packages/e2e-tests",
Expand Down
5 changes: 5 additions & 0 deletions packages/dependency-extraction-webpack-plugin/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Unreleased

### New Feature

- Introduce the `@wordpress/dependency-extraction-webpack-plugin` package.
196 changes: 196 additions & 0 deletions packages/dependency-extraction-webpack-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
# Dependency Extraction Webpack Plugin

This webpack plugin serves two purposes:

- Externalize dependencies that are available as script dependencies on modern WordPress sites.
- Add a JSON file for each entrypoint that declares the WordPress script dependencies for the
entrypoint.

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

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

## Installation

Install the module

```bash
npm install @wordpress/dependency-extraction-webpack-plugin --save-dev
```

## Usage

### Webpack

Use this plugin as you would other webpack plugins:

```js
// webpack.config.js
const WordPressExternalDependenciesPlugin = require( '@wordpress/dependency-extraction-webpack-plugin' );

module.exports = {
// …snip
plugins: [
new WordPressExternalDependenciesPlugin(),
]
}
```

Each entrypoint in the webpack bundle will include JSON file that declares the WordPress script dependencies that should be enqueued.
sirreal marked this conversation as resolved.
Show resolved Hide resolved

For example:

```
// Source file entrypoint.js
import { Component } from '@wordpress/element';

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

// Webpack will also produce output/entrypoint.deps.json declaring script dependencies
['wp-element']
```

By default, the following module requests are handled:

| Request | Global | Script handle |
| --- | --- | --- |
| `@babel/runtime/regenerator` | `regeneratorRuntime` | `wp-polyfill` |
| `@wordpress/*` | `wp['*']` | `wp-*` |
| `jquery` | `jQuery` | `jquery` |
| `jquery` | `jQuery` | `jquery` |
| `lodash-es` | `lodash` | `lodash` |
| `lodash` | `lodash` | `lodash` |
| `moment` | `moment` | `moment` |
| `react-dom` | `ReactDOM` | `react-dom` |
| `react` | `React` | `react` |

**Note:** This plugin overlaps with the functionality provided by [webpack
`externals`](https://webpack.js.org/configuration/externals). This plugin is intended to extract
script handles from bundle compilation so that a list of script dependencies does not need to be
manually maintained. If you don't need to extract a list of script 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.

#### Options

An object can be passed to the constructor to customize the behavior, for example:

```js
module.exports = {
plugins: [
new WordPressExternalDependenciesPlugin( { injectPolyfill: true } ),
]
}
```

##### `useDefaults`

- Type: boolean
- Default: `true`

Pass `useDefaults: false` to disable the default request handling.

##### `injectPolyfill`

- Type: boolean
- Default: `false`

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

##### `requestToExternal`

- 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' ]`.

`requestToExternal` 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 requestToExternal( request ) {

// Handle imports like `import myModule from 'my-module'`
if ( request === 'my-module' ) {
// Expect to find `my-module` as myModule in the global scope:
return 'myModule';
}
}

module.exports = {
plugins: [
new WordPressExternalDependenciesPlugin( { requestToExternal } ),
]
}
```

##### `requestToHandle`

- Type: function

All of the external modules handled by the plugin are expected to be WordPress script dependencies
and will be added to the dependency list. `requestToHandle` allows the script handle included in the dependency list to be customized.

If no string is returned, the script handle is assumed to be the same as the request.

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

```js
/**
* Map 'my-module' request to 'my-module-script-handle'
*
* @param {string} request Requested module
*
* @return {(string|undefined)} Script global
*/
function requestToHandle( request ) {

// Handle imports like `import myModule from 'my-module'`
if ( request === 'my-module' ) {
// Expect to find `my-module` as myModule in the global scope:
return 'my-module-script-handle';
}
}

module.exports = {
plugins: [
new WordPressExternalDependenciesPlugin( { requestToExternal } ),
]
}
```

##### `requestToExternal` and `requestToHandle`

The functions `requestToExternal` and `requestToHandle` allow this module to handle arbitrary
modules. `requestToExternal` is necessary to handle any module and maps a module request to a global
name. `requestToHandle` maps the same module request to a script handle, the strings that will be
included in the `entrypoint.deps.json` files.

### WordPress

Enqueue your script as usual and read the script dependencies dynamically:

```php
$script_path = 'path/to/script.js';
$script_deps_path = 'path/to/script.deps.json';
$script_dependencies = file_exists( $script_deps_path )
? json_decode( file_get_contents( $script_deps_path ) )
: array();
$script_url = plugins_url( $script_path, __FILE__ );
wp_enqueue_script( 'script', $script_url, $script_dependencies );
```
Loading