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

Build: Prepare for more Script Modules #7360

Closed
Closed
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ wp-tests-config.php
/src/wp-admin/js
/src/wp-includes/assets/*
!/src/wp-includes/assets/script-loader-packages.min.php
!/src/wp-includes/assets/script-modules-packages.min.php
/src/wp-includes/js
/src/wp-includes/css/dist
/src/wp-includes/css/*.min.css
Expand Down
1 change: 1 addition & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ module.exports = function(grunt) {
'wp-includes/css/dist',
'wp-includes/blocks/**/*.css',
'!wp-includes/assets/script-loader-packages.min.php',
'!wp-includes/assets/script-modules-packages.min.php',
],

// Prepend `dir` to `file`, and keep `!` in place.
Expand Down
1 change: 1 addition & 0 deletions src/wp-includes/assets/script-modules-packages.min.php
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?php return array('interactivity/index.min.js' => array('dependencies' => array(), 'version' => '2d6d1fdbcb3fda39c768', 'type' => 'module'), 'interactivity/debug.min.js' => array('dependencies' => array(), 'version' => '1ccc67b05c275e51a8f8', 'type' => 'module'), 'interactivity-router/index.min.js' => array('dependencies' => array('@wordpress/interactivity'), 'version' => '64645ef3cd2d32860d7d', 'type' => 'module'), 'block-library/file/view.min.js' => array('dependencies' => array('@wordpress/interactivity'), 'version' => 'fdc2f6842e015af83140', 'type' => 'module'), 'block-library/image/view.min.js' => array('dependencies' => array('@wordpress/interactivity'), 'version' => 'acfec7b3c0be4a859b31', 'type' => 'module'), 'block-library/navigation/view.min.js' => array('dependencies' => array('@wordpress/interactivity'), 'version' => '8ff192874fc8910a284c', 'type' => 'module'), 'block-library/query/view.min.js' => array('dependencies' => array('@wordpress/interactivity', array('id' => '@wordpress/interactivity-router', 'import' => 'dynamic')), 'version' => 'f4c91c89fa5271f3dad9', 'type' => 'module'), 'block-library/search/view.min.js' => array('dependencies' => array('@wordpress/interactivity'), 'version' => '2a73400a693958f604de', 'type' => 'module'));
1 change: 1 addition & 0 deletions src/wp-includes/default-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,7 @@
// Script Loader.
add_action( 'wp_default_scripts', 'wp_default_scripts' );
add_action( 'wp_default_scripts', 'wp_default_packages' );
add_action( 'wp_default_scripts', 'wp_default_script_modules' );

add_action( 'wp_enqueue_scripts', 'wp_localize_jquery_ui_datepicker', 1000 );
add_action( 'wp_enqueue_scripts', 'wp_common_block_scripts_and_styles' );
Expand Down
19 changes: 3 additions & 16 deletions src/wp-includes/interactivity-api/class-wp-interactivity-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -281,33 +281,20 @@ public function get_context( ?string $store_namespace = null ): array {
/**
* Registers the `@wordpress/interactivity` script modules.
*
* @deprecated 6.7.0 Script Modules registration is handled by {@see wp_default_script_modules()}.
*
* @since 6.5.0
*/
public function register_script_modules() {
$suffix = wp_scripts_get_suffix();

wp_register_script_module(
'@wordpress/interactivity',
includes_url( "js/dist/interactivity$suffix.js" )
);

wp_register_script_module(
'@wordpress/interactivity-router',
includes_url( "js/dist/interactivity-router$suffix.js" ),
array( '@wordpress/interactivity' )
);
_deprecated_function( __METHOD__, '6.7.0', 'wp_default_script_modules' );
}

/**
* Adds the necessary hooks for the Interactivity API.
*
* @since 6.5.0
* @since 6.7.0 Use the {@see "script_module_data_{$module_id}"} filter to pass client-side data.
*/
public function add_hooks() {
add_action( 'wp_enqueue_scripts', array( $this, 'register_script_modules' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'register_script_modules' ) );

add_filter( 'script_module_data_@wordpress/interactivity', array( $this, 'filter_script_module_interactivity_data' ) );
Copy link
Member

@gziolo gziolo Sep 20, 2024

Choose a reason for hiding this comment

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

I’m not sure if this should still live here. Could get wired in wp_default_script_modules next to handlers for script modules. No strong preferences, though.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good question. My first thought is that this is specific to Interactivity API and it should manage its script module data filters, but it's true that other script modules may have their data filters added in a more generic place.

I'm inclined to leave it now just because there's no obvious reason to move it now. Maybe in time these core script module data filters should all be registered in one place if there are many of them.

I don't feel strongly one way or another.

}

Expand Down
50 changes: 50 additions & 0 deletions src/wp-includes/script-modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,53 @@ function wp_dequeue_script_module( string $id ) {
function wp_deregister_script_module( string $id ) {
wp_script_modules()->deregister( $id );
}

/**
* Registers all the default WordPress Script Modules.
*
* @since 6.7.0
*/
function wp_default_script_modules() {
$suffix = defined( 'WP_RUN_CORE_TESTS' ) ? '.min' : wp_scripts_get_suffix();

/*
* Expects multidimensional array like:
*
* 'interactivity/index.min.js' => array('dependencies' => array(…), 'version' => '…'),
* 'interactivity/debug.min.js' => array('dependencies' => array(…), 'version' => '…'),
* 'interactivity-router/index.min.js' => …
*/
$assets = include ABSPATH . WPINC . "/assets/script-modules-packages{$suffix}.php";

foreach ( $assets as $file_name => $script_module_data ) {
/*
* Build the WordPress Script Module ID from the file name.
* Prepend `@wordpress/` and remove extensions and `/index` if present:
* - interactivity/index.min.js => @wordpress/interactivity
* - interactivity/debug.min.js => @wordpress/interactivity/debug
* - block-library/query/view.js => @wordpress/block-library/query/view
Comment on lines +148 to +150
Copy link
Member

Choose a reason for hiding this comment

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

I'm wondering if these paths are the same on Windows. The challenge is that for regular scripts, the path contains only the file name. In this case, it's also folders so let's confirm that there isn't block-library\query\view.js or something like that. This would mean script-modules-packages.min.php under version control differs between operating systems.

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe @t-hamano could help with that like in WordPress/gutenberg#65064 (comment).

After npm run build, here's what's expected at build/wp-includes/js/dist/script-modules

# tree build/wp-includes/js/dist/script-modules
build/wp-includes/js/dist/script-modules
├── block-library
│   ├── file
│   │   ├── view.js
│   │   └── view.min.js
│   ├── image
│   │   ├── view.js
│   │   └── view.min.js
│   ├── navigation
│   │   ├── view.js
│   │   └── view.min.js
│   ├── query
│   │   ├── view.js
│   │   └── view.min.js
│   └── search
│       ├── view.js
│       └── view.min.js
├── interactivity
│   ├── debug.js
│   ├── debug.min.js
│   ├── index.js
│   └── index.min.js
└── interactivity-router
    ├── index.js
    └── index.min.js

9 directories, 16 files

Choose a reason for hiding this comment

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

I tested this PR on a Windows host OS. From my understanding, build:dev does not generate a build directory, but generates some directories in the src directory. On the other hand, the build command generates a complete WordPress in the build directory.

In both commands, the script-modules-packages.min.php file contains only forward slashes.

npm run build:dev

build-dev-1

build-dev-2

npm run build

build-1

build-2

I then replaced the core files in my local WordPress environment on my Windows host OS with the files built in this PR.

The image lightbox works and scripts seem to be enqueued correctly. The navigation block works fine too.

front-end

Copy link
Member Author

Choose a reason for hiding this comment

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

Thank you 👍

In both commands, the script-modules-packages.min.php file contains only forward slashes.

Perfect, so that file should be stable across different systems and working correctly 👌

Copy link
Member

Choose a reason for hiding this comment

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

@t-hamano, thank you so much for testing and confirming it works the same on Windows.

*/
$script_module_id = '@wordpress/' . preg_replace( '~(?:/index)?(?:\.min)?\.js$~D', '', $file_name, 1 );

switch ( $script_module_id ) {
/*
* Interactivity exposes two entrypoints, "/index" and "/debug".
* "/debug" should replalce "/index" in devlopment.
*/
case '@wordpress/interactivity/debug':
if ( ! SCRIPT_DEBUG ) {
continue 2;
}
$script_module_id = '@wordpress/interactivity';
break;
case '@wordpress/interactivity':
if ( SCRIPT_DEBUG ) {
continue 2;
}
break;
}

$path = "/wp-includes/js/dist/script-modules/{$file_name}";
wp_register_script_module( $script_module_id, $path, $script_module_data['dependencies'], $script_module_data['version'] );
}
}
12 changes: 11 additions & 1 deletion tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,17 @@ public function test_config_not_printed_when_empty() {
$this->expectOutputString( '' );
}

/**
* Test that the deprecated register_script_modules method is deprecated but does not throw.
*
* @ticket 60647
*
* @expectedDeprecated WP_Interactivity_API::register_script_modules
*/
public function test_register_script_modules_deprecated() {
$this->interactivity->register_script_modules();
}

/**
* Sets up an activity, runs an optional callback, and returns a MockAction for inspection.
*
Expand All @@ -221,7 +232,6 @@ public function test_config_not_printed_when_empty() {
*/
private function get_script_data_filter_result( ?Closure $callback = null ): MockAction {
$this->interactivity->add_hooks();
$this->interactivity->register_script_modules();
wp_enqueue_script_module( '@wordpress/interactivity' );
$filter = new MockAction();
add_filter( 'script_module_data_@wordpress/interactivity', array( $filter, 'filter' ) );
Expand Down
76 changes: 0 additions & 76 deletions tools/webpack/modules.js

This file was deleted.

119 changes: 119 additions & 0 deletions tools/webpack/script-modules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* External dependencies
*/
const { createRequire } = require( 'node:module' );
const { dirname } = require( 'node:path' );

/**
* WordPress dependencies
*/
const DependencyExtractionPlugin = require( '@wordpress/dependency-extraction-webpack-plugin' );

/**
* Internal dependencies
*/
const {
baseDir,
getBaseConfig,
normalizeJoin,
MODULES,
SCRIPT_AND_MODULE_DUAL_PACKAGES,
WORDPRESS_NAMESPACE,
} = require( './shared' );

/** @type {Map<string, string>} */
const scriptModules = new Map();
for ( const packageName of MODULES.concat( SCRIPT_AND_MODULE_DUAL_PACKAGES ) ) {
const packageRequire = createRequire(
`${ dirname( require.resolve( `${ packageName }/package.json` ) ) }/`
);

const depPackageJson = packageRequire( './package.json' );
if ( ! Object.hasOwn( depPackageJson, 'wpScriptModuleExports' ) ) {
continue;
}

const moduleName = packageName.substring( WORDPRESS_NAMESPACE.length );
let { wpScriptModuleExports } = depPackageJson;

// Special handling for { "wpScriptModuleExports": "./build-module/index.js" }.
if ( typeof wpScriptModuleExports === 'string' ) {
wpScriptModuleExports = { '.': wpScriptModuleExports };
}

if ( Object.getPrototypeOf( wpScriptModuleExports ) !== Object.prototype ) {
throw new Error( 'wpScriptModuleExports must be an object' );
}

for ( const [ exportName, exportPath ] of Object.entries(
wpScriptModuleExports
) ) {
if ( typeof exportPath !== 'string' ) {
throw new Error( 'wpScriptModuleExports paths must be strings' );
}

if ( ! exportPath.startsWith( './' ) ) {
throw new Error(
'wpScriptModuleExports paths must start with "./"'
);
}

const name =
exportName === '.' ? 'index' : exportName.replace( /^\.\/?/, '' );

scriptModules.set(
`${ moduleName }/${ name }`,
packageRequire.resolve( exportPath )
);
}
}

module.exports = function (
env = { environment: 'production', watch: false, buildTarget: false }
) {
const mode = env.environment;
const suffix = mode === 'production' ? '.min' : '';
let buildTarget = env.buildTarget
? env.buildTarget
: mode === 'production'
? 'build'
: 'src';
buildTarget = buildTarget + '/wp-includes';

const baseConfig = getBaseConfig( env );
const config = {
...baseConfig,
entry: Object.fromEntries( scriptModules.entries() ),
experiments: {
outputModule: true,
},
output: {
devtoolNamespace: 'wp',
filename: `[name]${ suffix }.js`,
path: normalizeJoin(
baseDir,
`${ buildTarget }/js/dist/script-modules`
),
library: {
type: 'module',
},
environment: { module: true },
module: true,
chunkFormat: 'module',
asyncChunks: false,
},
plugins: [
...baseConfig.plugins,
new DependencyExtractionPlugin( {
injectPolyfill: false,
combineAssets: true,
combinedOutputFile: normalizeJoin(
baseDir,
`${ buildTarget }/assets/script-modules-packages${ suffix }.php`
),
} ),
],
};

return config;
};
4 changes: 4 additions & 0 deletions tools/webpack/shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ const MODULES = [
'@wordpress/interactivity',
'@wordpress/interactivity-router',
];
const SCRIPT_AND_MODULE_DUAL_PACKAGES = [
'@wordpress/block-library',
];
const WORDPRESS_NAMESPACE = '@wordpress/';

module.exports = {
Expand All @@ -111,5 +114,6 @@ module.exports = {
stylesTransform,
BUNDLED_PACKAGES,
MODULES,
SCRIPT_AND_MODULE_DUAL_PACKAGES,
WORDPRESS_NAMESPACE,
};
4 changes: 2 additions & 2 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const blocksConfig = require( './tools/webpack/blocks' );
const developmentConfig = require( './tools/webpack/development' );
const mediaConfig = require( './tools/webpack/media' );
const packagesConfig = require( './tools/webpack/packages' );
const modulesConfig = require( './tools/webpack/modules' );
const scriptModulesConfig = require( './tools/webpack/script-modules' );
const vendorsConfig = require( './tools/webpack/vendors' );

module.exports = function( env = { environment: "production", watch: false, buildTarget: false } ) {
Expand All @@ -19,7 +19,7 @@ module.exports = function( env = { environment: "production", watch: false, buil
...developmentConfig( env ),
mediaConfig( env ),
packagesConfig( env ),
modulesConfig( env ),
scriptModulesConfig( env ),
...vendorsConfig( env ),
];

Expand Down
Loading