diff --git a/admin/load.php b/admin/load.php index 2789bd53d9..11b27a98fe 100644 --- a/admin/load.php +++ b/admin/load.php @@ -200,35 +200,49 @@ function perflab_get_modules( $modules_root = null ) { $module_files = array(); $modules_dir = @opendir( $modules_root ); + // Modules are organized as {focus}/{module-slug} in the modules folder. if ( $modules_dir ) { // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition - while ( ( $file = readdir( $modules_dir ) ) !== false ) { - if ( '.' === substr( $file, 0, 1 ) ) { + while ( ( $focus = readdir( $modules_dir ) ) !== false ) { + if ( '.' === substr( $focus, 0, 1 ) ) { continue; } - // Unlike plugins, modules must be in a directory. - if ( ! is_dir( $modules_root . '/' . $file ) ) { + // Each focus area must be a directory. + if ( ! is_dir( $modules_root . '/' . $focus ) ) { continue; } - $module_dir = @opendir( $modules_root . '/' . $file ); - if ( $module_dir ) { + $focus_dir = @opendir( $modules_root . '/' . $focus ); + if ( $focus_dir ) { // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition - while ( ( $subfile = readdir( $module_dir ) ) !== false ) { - if ( '.' === substr( $subfile, 0, 1 ) ) { + while ( ( $file = readdir( $focus_dir ) ) !== false ) { + // Unlike plugins, modules must be in a directory. + if ( ! is_dir( $modules_root . '/' . $focus . '/' . $file ) ) { continue; } - // Unlike plugins, module main files must be called `load.php`. - if ( 'load.php' !== $subfile ) { - continue; - } + $module_dir = @opendir( $modules_root . '/' . $focus . '/' . $file ); + if ( $module_dir ) { + // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition + while ( ( $subfile = readdir( $module_dir ) ) !== false ) { + if ( '.' === substr( $subfile, 0, 1 ) ) { + continue; + } + + // Unlike plugins, module main files must be called `load.php`. + if ( 'load.php' !== $subfile ) { + continue; + } - $module_files[] = "$file/$subfile"; + $module_files[] = "$focus/$file/$subfile"; + } + + closedir( $module_dir ); + } } - closedir( $module_dir ); + closedir( $focus_dir ); } } @@ -239,13 +253,13 @@ function perflab_get_modules( $modules_root = null ) { if ( ! is_readable( "$modules_root/$module_file" ) ) { continue; } - + $module_dir = dirname( $module_file ); $module_data = perflab_get_module_data( "$modules_root/$module_file" ); if ( ! $module_data ) { continue; } - $modules[ dirname( $module_file ) ] = $module_data; + $modules[ $module_dir ] = $module_data; } uasort( @@ -271,10 +285,13 @@ function( $a, $b ) { * 'name', 'description', 'focus', and 'experimental'. */ function perflab_get_module_data( $module_file ) { + // Extract the module dir in the form {focus}/{module-slug}. + preg_match( '/.*\/(.*\/.*)\/load\.php$/i', $module_file, $matches ); + $module_dir = $matches[1]; + $default_headers = array( 'name' => 'Module Name', 'description' => 'Description', - 'focus' => 'Focus', 'experimental' => 'Experimental', ); @@ -292,6 +309,13 @@ function perflab_get_module_data( $module_file ) { $module_data['experimental'] = false; } + // Extract the module focus from the module directory. + if ( strpos( $module_dir, '/' ) ) { + list( $focus, $slug ) = explode( '/', $module_dir ); + $module_data['focus'] = $focus; + $module_data['slug'] = $slug; + } + // Translate fields using low-level function since they come from PHP comments, including the necessary context for // `_x()`. This must match how these are translated in the generated `/module-i18n.php` file. $translatable_fields = array( diff --git a/bin/plugin/commands/translations.js b/bin/plugin/commands/translations.js index f9afa1bb6d..8fb0283984 100644 --- a/bin/plugin/commands/translations.js +++ b/bin/plugin/commands/translations.js @@ -77,7 +77,7 @@ exports.handler = async ( opt ) => { * @return {[]WPTranslationEntry} List of translation entries. */ async function getTranslations( settings ) { - const moduleFilePattern = path.join( settings.directory, '*/load.php' ); + const moduleFilePattern = path.join( settings.directory, '*/*/load.php' ); const moduleFiles = await glob( path.resolve( '.', moduleFilePattern ) ); const moduleTranslations = moduleFiles diff --git a/docs/Writing-a-module.md b/docs/Writing-a-module.md index a09d044118..d4b814004e 100644 --- a/docs/Writing-a-module.md +++ b/docs/Writing-a-module.md @@ -13,18 +13,17 @@ Every module surfaces on the admin settings page of the performance plugin, wher ## Module requirements -* The production code for a module must all be located in a directory `/modules/{module-slug}` where `{module-slug}` is the module's slug. -* The entry point file must be called `load.php` and per the above be located at `/modules/{module-slug}/load.php`. +* The production code for a module must all be located in a directory `/modules/{focus}/{module-slug}` where `{module-slug}` is the module's slug and `{focus}` is the focus area: an identifier of a single focus (e.g. `images`). This should correspond to a section on the performance plugin's settings page. [See the `perflab_get_focus_areas()` function for the currently available focus areas.](../admin/load.php#L161) +* The entry point file must be called `load.php` and per the above be located at `/modules/{focus}/{module-slug}/load.php`. * The `load.php` entry point file must contain a module header with the following fields: * `Module Name`: Name of the module (comparable to `Plugin Name` for plugins). It will be displayed on the performance plugin's settings page. * `Description`: Brief description of the module (comparable to `Description` for plugins). It will be displayed next to the module name on the performance plugin's settings page. - * `Focus`: Identifier of a single focus area (e.g. `images`). This should correspond to a section on the performance plugin's settings page. [See the `perflab_get_focus_areas()` function for the currently available focus areas.](../admin/load.php#L161) * `Experimental`: Either `Yes` or `No`. If `Yes`, the module will be marked as explicitly experimental on the performance plugin's settings page. While all modules are somewhat experimental (similar to feature plugins), for some that may apply more than for others. For example, certain modules we would encourage limited testing in production for, where we've already established a certain level of reliability/quality, in other cases modules shouldn't be used in production at all. * The module must neither rely on any PHP code from outside its directory nor on any external PHP code. If relying on an external PHP dependency is essential for a module, the approach should be evaluated and discussed with the wider team. * The module must use the `performance-lab` text domain for all of its localizable strings. * All global code structures in the module PHP codebase must be prefixed (e.g. with a string based on the module slug) to avoid conflicts with other modules or plugins. -* All test code for a module (e.g. PHPUnit tests) must be located in a directory `/tests/modules/{module-slug}` where `{module-slug}` is the module's slug (i.e. the same folder name used above). - * If tests require some test-specific structures (e.g. dummy data or mock classes), those should be implemented in a directory `/tests/testdata/modules/{module-slug}`. +* All test code for a module (e.g. PHPUnit tests) must be located in a directory `/tests/modules/{focus}/{module-slug}` where `{module-slug}` is the module's slug and `{focus}` is the focus area (i.e. the same folder names used above). + * If tests require some test-specific structures (e.g. dummy data or mock classes), those should be implemented in a directory `/tests/testdata/modules/{focus}/{module-slug}`. * The module must adhere to the WordPress coding and documentation standards. ## Module recommendations @@ -35,14 +34,13 @@ Every module surfaces on the admin settings page of the performance plugin, wher ## Example -The following is a minimum module entry point file `/modules/my-module/load.php` (i.e. the module slug is "my-module"): +The following is a minimum module entry point file `/modules/images/my-module/load.php` (i.e. the module slug is "my-module", and the focus is "images"): ```php array( + 'javascript/demo-module-1' => array( 'name' => 'Demo Module 1', 'description' => 'This is the description for demo module 1.', - 'focus' => 'javascript', 'experimental' => false, + 'focus' => 'javascript', + 'slug' => 'demo-module-1', ), - 'demo-module-2' => array( + 'something/demo-module-2' => array( 'name' => 'Demo Module 2', 'description' => 'This is the description for demo module 2.', - 'focus' => 'something', 'experimental' => true, + 'focus' => 'something', + 'slug' => 'demo-module-2', ), - 'demo-module-3' => array( + 'images/demo-module-3' => array( 'name' => 'Demo Module 3', 'description' => 'This is the description for demo module 3.', - 'focus' => 'images', 'experimental' => false, + 'focus' => 'images', + 'slug' => 'demo-module-3', ), ); @@ -116,15 +119,15 @@ public function test_perflab_load_modules_page() { array_keys( $wp_settings_fields[ PERFLAB_MODULES_SCREEN ] ) ); $this->assertEqualSets( - array( 'demo-module-3' ), + array( 'images/demo-module-3' ), array_keys( $wp_settings_fields[ PERFLAB_MODULES_SCREEN ]['images'] ) ); $this->assertEqualSets( - array( 'demo-module-1' ), + array( 'javascript/demo-module-1' ), array_keys( $wp_settings_fields[ PERFLAB_MODULES_SCREEN ]['javascript'] ) ); $this->assertEqualSets( - array( 'demo-module-2' ), + array( 'something/demo-module-2' ), array_keys( $wp_settings_fields[ PERFLAB_MODULES_SCREEN ]['other'] ) ); } @@ -138,7 +141,7 @@ public function test_perflab_render_modules_page() { } public function test_perflab_render_modules_page_field() { - $module_slug = 'demo-module-1'; + $module_slug = 'javascript/demo-module-1'; $module_data = self::$demo_modules[ $module_slug ]; $module_settings = array( 'enabled' => false ); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index ff6bf784ae..b615fc636f 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -43,7 +43,7 @@ 'plugins_loaded', function() { require_once TESTS_PLUGIN_DIR . '/admin/load.php'; - $module_files = glob( TESTS_PLUGIN_DIR . '/modules/*/load.php' ); + $module_files = glob( TESTS_PLUGIN_DIR . '/modules/*/*/load.php' ); if ( $module_files ) { foreach ( $module_files as $module_file ) { require_once $module_file; diff --git a/tests/modules/webp-uploads/webp-uploads-test.php b/tests/modules/images/webp-uploads/webp-uploads-test.php similarity index 100% rename from tests/modules/webp-uploads/webp-uploads-test.php rename to tests/modules/images/webp-uploads/webp-uploads-test.php diff --git a/tests/testdata/demo-modules/demo-module-3/load.php b/tests/testdata/demo-modules/images/demo-module-3/load.php similarity index 92% rename from tests/testdata/demo-modules/demo-module-3/load.php rename to tests/testdata/demo-modules/images/demo-module-3/load.php index 5acbe73b1d..007ba59552 100644 --- a/tests/testdata/demo-modules/demo-module-3/load.php +++ b/tests/testdata/demo-modules/images/demo-module-3/load.php @@ -2,7 +2,6 @@ /** * Module Name: Demo Module 3 * Description: This is the description for demo module 3. - * Focus: images * Experimental: No * * @package performance-lab diff --git a/tests/testdata/demo-modules/demo-module-1/load.php b/tests/testdata/demo-modules/javascript/demo-module-1/load.php similarity index 90% rename from tests/testdata/demo-modules/demo-module-1/load.php rename to tests/testdata/demo-modules/javascript/demo-module-1/load.php index 2ca7a19009..e1cdb99aa4 100644 --- a/tests/testdata/demo-modules/demo-module-1/load.php +++ b/tests/testdata/demo-modules/javascript/demo-module-1/load.php @@ -2,7 +2,6 @@ /** * Module Name: Demo Module 1 * Description: This is the description for demo module 1. - * Focus: javascript * Experimental: No * * @package performance-lab diff --git a/tests/testdata/demo-modules/demo-module-2/load.php b/tests/testdata/demo-modules/something/demo-module-2/load.php similarity index 90% rename from tests/testdata/demo-modules/demo-module-2/load.php rename to tests/testdata/demo-modules/something/demo-module-2/load.php index 2f76cb23ad..5e8e698a9a 100644 --- a/tests/testdata/demo-modules/demo-module-2/load.php +++ b/tests/testdata/demo-modules/something/demo-module-2/load.php @@ -2,7 +2,6 @@ /** * Module Name: Demo Module 2 * Description: This is the description for demo module 2. - * Focus: something * Experimental: Yes * * @package performance-lab diff --git a/tests/testdata/modules/webp-uploads/class-wp-image-doesnt-support-webp.php b/tests/testdata/modules/images/webp-uploads/class-wp-image-doesnt-support-webp.php similarity index 100% rename from tests/testdata/modules/webp-uploads/class-wp-image-doesnt-support-webp.php rename to tests/testdata/modules/images/webp-uploads/class-wp-image-doesnt-support-webp.php diff --git a/tests/testdata/modules/webp-uploads/class-wp-image-supports-webp.php b/tests/testdata/modules/images/webp-uploads/class-wp-image-supports-webp.php similarity index 100% rename from tests/testdata/modules/webp-uploads/class-wp-image-supports-webp.php rename to tests/testdata/modules/images/webp-uploads/class-wp-image-supports-webp.php