Skip to content

Commit

Permalink
Merge pull request #134 from wp-graphql/fix/issue-124-drawer-delay
Browse files Browse the repository at this point in the history
fix: Defer script to reduce the initial delay w/ opening of drawer
  • Loading branch information
josephfusco authored May 7, 2024
2 parents c40da05 + b543fe1 commit 4433643
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 44 deletions.
8 changes: 8 additions & 0 deletions .changeset/afraid-dolphins-joke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"wpgraphql-ide": patch
---

- fix: The IDE no longer waits on `DOMContentLoaded` in order to help client side performance with heavier pages.
- add: New PHP filters for updating the drawer label:
- `wpgraphqlide_drawer_button_label`
- `wpgraphqlide_drawer_button_loading_label`
3 changes: 3 additions & 0 deletions package-lock.json

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

9 changes: 6 additions & 3 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* global WPGRAPHQL_IDE_DATA */
import { createRoot, useEffect } from '@wordpress/element';
import { useEffect } from '@wordpress/element';
import { doAction } from '@wordpress/hooks';
import { useDispatch, useSelect } from '@wordpress/data';
import { parse, print } from 'graphql';
Expand All @@ -8,7 +8,10 @@ import LZString from 'lz-string';
import { EditorDrawer } from './components/EditorDrawer';
import { Editor } from './components/Editor';

const { isDedicatedIdePage } = window.WPGRAPHQL_IDE_DATA;
const {
isDedicatedIdePage,
context: { drawerButtonLabel },
} = window.WPGRAPHQL_IDE_DATA;

const url = new URL( window.location.href );
const params = url.searchParams;
Expand Down Expand Up @@ -129,7 +132,7 @@ export function RenderApp() {

return (
<div className="AppRoot">
<EditorDrawer>
<EditorDrawer buttonLabel={ drawerButtonLabel }>
<Editor />
</EditorDrawer>
</div>
Expand Down
5 changes: 2 additions & 3 deletions src/components/Editor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ import { GraphiQL } from 'graphiql';
import { useDispatch, useSelect } from '@wordpress/data';
import { parse, visit } from 'graphql';
import { explorerPlugin } from '@graphiql/plugin-explorer';
import { helpPlugin } from './help'
import { helpPlugin } from './help';

import { PrettifyButton } from './toolbarButtons/PrettifyButton';
import { CopyQueryButton } from './toolbarButtons/CopyQueryButton';
import { MergeFragmentsButton } from './toolbarButtons/MergeFragmentsButton';
import { ShareDocumentButton } from './toolbarButtons/ShareDocumentButton';
import { ToggleAuthButton } from './toolbarButtons/ToggleAuthButton';


import 'graphiql/graphiql.min.css';

/**
Expand Down Expand Up @@ -129,7 +128,7 @@ export function Editor() {
setSchema( newSchema );
}
} }
plugins={[ explorer, help ]}
plugins={ [ explorer, help ] }
>
<GraphiQL.Toolbar>
<ToggleAuthButton
Expand Down
4 changes: 1 addition & 3 deletions src/components/EditorDrawer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import React from 'react';
import { Drawer as VaulDrawer } from 'vaul';
import { useDispatch, useSelect } from '@wordpress/data';

export function EditorDrawer( { children } ) {
const buttonLabel = '🚀';

export function EditorDrawer( { children, buttonLabel } ) {
const isDrawerOpen = useSelect( ( select ) => {
return select( 'wpgraphql-ide' ).isDrawerOpen();
} );
Expand Down
8 changes: 4 additions & 4 deletions src/components/help/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ export const helpPlugin = () => {
title: 'Help',
icon: () => (
<Icon
icon={help}
style={{
icon={ help }
style={ {
fill: 'hsla(var(--color-neutral), var(--alpha-tertiary))',
}}
} }
/>
),
content: () => <HelpPanel/>,
content: () => <HelpPanel />,
};
};
14 changes: 9 additions & 5 deletions src/components/toolbarButtons/ToggleAuthButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,15 @@ export const ToggleAuthButton = ( {

return (
<ToolbarButton
className={ clsx( 'graphiql-un-styled', 'graphiql-toolbar-button graphiql-auth-button', {
[ styles.authAvatarPublic ]: ! isAuthenticated,
'is-authenticated': isAuthenticated,
'is-public': ! isAuthenticated,
} ) }
className={ clsx(
'graphiql-un-styled',
'graphiql-toolbar-button graphiql-auth-button',
{
[ styles.authAvatarPublic ]: ! isAuthenticated,
'is-authenticated': isAuthenticated,
'is-public': ! isAuthenticated,
}
) }
onClick={ toggleAuthentication }
label={ title }
>
Expand Down
50 changes: 31 additions & 19 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,49 @@
// WordPress dependencies for hooks, data handling, and component rendering.
/**
* @file
* Initializes the WPGraphQL IDE by setting up the necessary WordPress hooks,
* registering the global store, and exposing the GraphQL functionality through a global IDE object.
*/

// External dependencies
import { createHooks } from '@wordpress/hooks';
import { register } from '@wordpress/data';
import { createRoot } from '@wordpress/element';
import * as GraphQL from "graphql/index.js";
import * as GraphQL from 'graphql/index.js';

// Local imports including the store configuration and the main App component.
// Internal dependencies
import { store } from './store';
import { App } from './App';

// Register the store with wp.data to make it available throughout the plugin.
/**
* Registers the application's data store with WordPress to enable global state management.
*/
register( store );

// Create a central event hook system for the WPGraphQL IDE.
/**
* Initializes a hooks system to extend the functionality of the WPGraphQL IDE through
* external scripts and internal plugin hooks.
*/
export const hooks = createHooks();

// Expose a global variable for the IDE, facilitating extension through external scripts.
/**
* Exposes a global `WPGraphQLIDE` variable that includes hooks, store, and GraphQL references,
* making them accessible for extensions and external scripts.
*/
window.WPGraphQLIDE = {
hooks,
store,
GraphQL
GraphQL,
};

/**
* Initialize and render the application once the DOM is fully loaded.
* This ensures that the application mounts when all page elements are available.
* Attempts to render the React application to a specified mount point in the DOM.
* Logs an error to the console if the mount point is missing.
*/
document.addEventListener( 'DOMContentLoaded', () => {
const appMountPoint = document.getElementById( 'wpgraphql-ide-root' );
if ( appMountPoint ) {
createRoot( appMountPoint ).render( <App /> );
} else {
console.error(
'WPGraphQL IDE mount point not found. Please ensure an element with ID "wpgraphql-ide-root" exists.'
);
}
} );
const appMountPoint = document.getElementById( 'wpgraphql-ide-root' );
if ( appMountPoint ) {
createRoot( appMountPoint ).render( <App /> );
} else {
console.error(
'WPGraphQL IDE mount point not found. Please ensure an element with ID "wpgraphql-ide-root" exists.'
);
}
26 changes: 26 additions & 0 deletions tests/e2e/specs/drawer.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
loginToWordPressAdmin,
openDrawer,
pasteVariables,
simulateHeavyJSLoad,
typeQuery,
typeVariables,
visitAdminFacingPage,
Expand Down Expand Up @@ -37,6 +38,31 @@ test( 'should open and close successfully', async ( { page } ) => {
).not.toBeVisible();
} );

test( 'should open on JS-heavy admin page with CPU Throttling', async ( { page } ) => {
await visitAdminFacingPage( page );

// Start a new CDP Session to control the browser
const context = page.context();
const session = await context.newCDPSession( page );

// Set CPU Throttling
await session.send( 'Emulation.setCPUThrottlingRate', { rate: 4 }); // Throttles CPU to 1/4th its speed

// Now simulate heavy JavaScript load
await simulateHeavyJSLoad( page );

// Check if the drawer is initially hidden
await expect( page.locator( '.graphiql-container' ) ).toBeHidden();

// Open the drawer and ensure it still functions under throttled conditions
await openDrawer( page );
await expect( page.locator( '.graphiql-container' ) ).toBeVisible();

// Optionally, reset CPU throttling rate after test
await session.send( 'Emulation.setCPUThrottlingRate', { rate: 1 } );
});


test( 'should execute a GraphQL query successfully', async ( { page } ) => {
await page.click( selectors.editorDrawerButton );
await expect( page.locator( selectors.graphiqlContainer ) ).toBeVisible();
Expand Down
20 changes: 20 additions & 0 deletions tests/e2e/specs/environment.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { test, expect } from '@playwright/test';
import { loginToWordPressAdmin, visitPluginsPage } from '../utils';

test.beforeEach(async ({ page }) => {
await loginToWordPressAdmin(page);
});

test( 'expect the enqueued wpgraphql-ide script to have the defer attribute', async ( { page } ) => {
await visitPluginsPage( page );

// Retrieve the script tags and check for the defer attribute
const hasDeferAttribute = await page.evaluate( () => {
const scripts = Array.from( document.querySelectorAll( 'script' ) );
const targetScript = scripts.find( script => script.src.includes( 'wpgraphql-ide' ) );
return targetScript?.hasAttribute( 'defer' );
});

// Assert that the defer attribute is present
expect( hasDeferAttribute ).toBe( true );
});
27 changes: 27 additions & 0 deletions tests/e2e/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,30 @@ async function selectAndClearTextUsingKeyboard( page, selector ) {
await page.keyboard.press( selectAllCommand ); // Select all text using OS-specific shortcut
await page.keyboard.press( 'Backspace' ); // Clear selected text
}

export async function simulateHeavyJSLoad(page) {
await page.evaluate(() => {
// Simulate heavy DOM manipulations
for (let i = 0; i < 500; i++) {
const div = document.createElement('div');
div.textContent = `Heavy content ${i}`;
div.style.backgroundColor = "#" + Math.floor(Math.random()*16777215).toString(16);
document.body.appendChild(div);
}

// Simulate heavy computations
const heavyComputation = Array.from({ length: 50000 }, (_, i) => i ** 2).reduce((a, b) => a + b);
console.log('Heavy computation result:', heavyComputation);

// Simulate asynchronous operations
new Promise(resolve => setTimeout(resolve, 5000)).then(() => console.log('Delayed operation completed'));

// Simulate frequent DOM updates
setInterval(() => {
const randomDiv = document.querySelector(`div:nth-child(${Math.floor(Math.random() * 500) + 1})`);
if (randomDiv) {
randomDiv.style.backgroundColor = "#" + Math.floor(Math.random()*16777215).toString(16);
}
}, 10);
});
}
44 changes: 37 additions & 7 deletions wpgraphql-ide.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
exit;
}

define( 'WPGRAPHQL_IDE_VERSION', '1.0.1' );
define( 'WPGRAPHQL_IDE_VERSION', '1.1.8' );
define( 'WPGRAPHQL_IDE_ROOT_ELEMENT_ID', 'wpgraphql-ide-root' );
define( 'WPGRAPHQL_IDE_PLUGIN_DIR_PATH', plugin_dir_path( __FILE__ ) );
define( 'WPGRAPHQL_IDE_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
Expand Down Expand Up @@ -117,6 +117,8 @@ function register_wpadminbar_menus(): void {

global $wp_admin_bar;

$app_context = get_app_context();

// Link to the new dedicated IDE page.
$wp_admin_bar->add_node(
[
Expand All @@ -131,7 +133,7 @@ function register_wpadminbar_menus(): void {
$wp_admin_bar->add_node(
[
'id' => 'wpgraphql-ide-button',
'title' => '<div id="' . esc_attr( WPGRAPHQL_IDE_ROOT_ELEMENT_ID ) . '"></div>',
'title' => '<div id="' . esc_attr( WPGRAPHQL_IDE_ROOT_ELEMENT_ID ) . '">' . $app_context['drawerButtonLoadingLabel'] . '</div>',
'href' => '#',
]
);
Expand Down Expand Up @@ -234,7 +236,7 @@ function enqueue_react_app_with_styles(): void {
plugins_url( 'build/index.js', __FILE__ ),
$asset_file['dependencies'],
$asset_file['version'],
true
false
);

wp_localize_script(
Expand Down Expand Up @@ -305,10 +307,12 @@ function get_app_context(): array {
return apply_filters(
'wpgraphqlide_context',
[
'pluginVersion' => get_plugin_header( 'Version' ),
'pluginName' => get_plugin_header( 'Name' ),
'externalFragments' => apply_filters( 'wpgraphqlide_external_fragments', [] ),
'avatarUrl' => $avatar_url,
'pluginVersion' => get_plugin_header( 'Version' ),
'pluginName' => get_plugin_header( 'Name' ),
'externalFragments' => apply_filters( 'wpgraphqlide_external_fragments', [] ),
'avatarUrl' => $avatar_url,
'drawerButtonLabel' => apply_filters( 'wpgraphqlide_drawer_button_label', '🚀' ),
'drawerButtonLoadingLabel' => apply_filters( 'wpgraphqlide_drawer_button_loading_label', '' ),
]
);
}
Expand Down Expand Up @@ -391,3 +395,29 @@ static function ( bool $is_plugin_scoped_page, string $current_page_id, array $a
10,
3
);

/**
* Modifies the script tag for specific scripts to add the 'defer' attribute.
*
* This function checks if the script handle matches 'wpgraphql-ide' and, if so,
* adds the 'defer' attribute to the script tag. This allows the script to be executed
* after the HTML document is parsed but before the DOMContentLoaded event.
*
* @param string $tag The HTML `<script>` tag of the enqueued script.
* @param string $handle The script's registered handle in WordPress.
*
* @return string Modified script tag with 'defer' attribute included if handle matches; otherwise, unchanged.
*/
add_filter(
'script_loader_tag',
static function ( string $tag, string $handle ) {

if ( 'wpgraphql-ide' === $handle ) {
return str_replace( ' src', ' defer="defer" src', $tag );
}

return $tag;
},
10,
2
);

0 comments on commit 4433643

Please sign in to comment.