Skip to content

Conversation

@jasonbahl
Copy link
Collaborator

@jasonbahl jasonbahl commented May 13, 2020

This Pull Request adds connections for enqueued assets (scripts and stylesheets) to the Graph.

Querying Enqueued Scripts and Stylesheets as Connections

When enqueueing assets to WordPress, the enqueued asset can be considered a connection to the nodes it is enqueued to.

Enqueue the assets

For example, enqueuing a script to just pages like so:

add_action( 'wp_enqueue_scripts', function() {

  // Register the script and stylesheet
  wp_register_script( 'my-script-for-pages', '/path/to/page-script.js' );
  wp_register_style( 'my-styles-for-pages', '/path/to/page-style.css' );

  // Enqueue the script and stylesheet on _only_ pages
  if ( is_page() ) {
    wp_enqueue_script( 'my-script-for-pages' );
    wp_enqueue_style( 'my-styles-for-pages' );
  }

});

This will add the /path/to/page-script.js' and /path/to/page-styles.css' files to any Page of the "page" post type in the front-end of WordPress, but nowhere else. Templates for tag archives, for example, would not have this CSS or JS added.

Often times content (that can be queried for headlessly) relies on these scripts (and styles). For example unordered lists that are converted to Slideshows or Lightbox galleries, etc.

Query the assets as connections

With this Pull Request, these enqueued assets will be able to be queried in GraphQL as connected nodes like so:

{
  pages(first: 1) {
    nodes {
      id
      title
      enqueuedScripts {
        nodes {
          id
          handle
          src
        }
      }
      enqueuedStylesheets {
        nodes {
          id
          handle
          src
        }
      }
    }
  }
}

Get the connected assets in response

This would return a payload like (any other scripts and styles enqueued to pages would also be included):

{
  "data": {
    "pages": {
      "nodes": [
        {
          "id": "cG9zdDo1MzU=",
          "title": "Page One",
          "enqueuedScripts": {
            "nodes": [
              {
                "id": "ZW5xdWV1ZWRfc2NyaXB0Om15LXNjcmlwdC1mb3ItcGFnZXM=",
                "handle": "my-script-for-pages",
                "src": "/path/to/page-script.js"
              }
            ]
          },
          "enqueuedStylesheets": {
            "nodes": [
              {
                "id": "ZW5xdWV1ZWRfc3R5bGVzaGVldDpteS1zdHlsZXMtZm9yLXBhZ2Vz",
                "handle": "my-styles-for-pages",
                "src": "/path/to/page-style.css"
              },
            ]
          }
        }
      ]
    }
  }

This allows for external sources that are consuming WordPress content over GraphQL to make use of associated styles and scripts that may compliment the content. This could, in theory, allow for external consumers such as Gatsby, Next, Gridsome, etc to work with Gutenberg or other Page Builders!

Root Queries

This also allows for assets to be queried from the root of the graph, like so:

{
  registeredScripts {
    nodes {
      id
      handle
      src
    }
  }
}

And get a payload like so:

{
  "data": {
    "registeredScripts": {
      "nodes": [
        {
          "id": "ZW5xdWV1ZWRfc2NyaXB0OnV0aWxz",
          "handle": "utils",
          "src": "/wp-includes/js/utils.min.js"
        },
        {
          "id": "ZW5xdWV1ZWRfc2NyaXB0OmNvbW1vbg==",
          "handle": "common",
          "src": "/wp-admin/js/common.min.js"
        },
        {
          "id": "ZW5xdWV1ZWRfc2NyaXB0OndwLXNhbml0aXpl",
          "handle": "wp-sanitize",
          "src": "/wp-includes/js/wp-sanitize.min.js"
        },
      ]
    }
  }
}

Additionally, we can take an ID of a resource and get it from a node query like so:

{
  node(id: "ZW5xdWV1ZWRfc2NyaXB0OndwLXNhbml0aXpl") {
    __typename
    ... on EnqueuedAsset {
      handle
      src
    }
  }
}

And get a payload like so:

{
  "data": {
    "node": {
      "__typename": "EnqueuedScript",
      "handle": "wp-sanitize",
      "src": "/wp-includes/js/wp-sanitize.min.js"
    }
  }
}

Known issues:

There are some "kinks" and imperfections to this approach, as WordPress at large has not exactly been preparing for a headless/decoupled future.

Improper registration/enqueing of scripts

Many plugins and themes, for example, don't register styles and scripts to the global registry, they just enqueue them. This can be problematic as the resource nodes for the assets won't be accessible via a node query using the asset ID, nor would they be accessible via a root { registeredScripts { nodes { ...fields } } query (because they're not actually registered).

The Storefront theme is an example of conditionally registering assets, which is also problematic. This means that a resource is only queryable via a connection and not available in a Root Query for all registered assets or via the node( id: $id ) query.

This will take some education around the ecosystem. . .Registering scripts and styles should be global, and enqueueing them should be conditional / contextual.

Updating Global IDs

The intent of Global IDs is to ensure nodes have unique IDs across Types, but also to provide proper context so the server can resolve a node from an ID using the node( id:$id) query.

Prior to this PR, the Global IDs were a hash of the WordPress Type (post, page, etc) and the database ID.

Now, the Global IDs are a hash of the Loader Name and the database ID.

So posts, pages, and custom post types will all be hashed with: post:$id, and tags, categories and custom taxonomies will all be hashed withterm:$id.

This change allowed for a lot of code around resolving nodes to be deleted! (@mngi-arogers would be proud)

Formalizing all Connections

Prior to this PR, there were multiple ways of handling connections. Many connections made use of the AbstractConnectionResolver class, but some made use of Relay::connectionFromArray(). The connectionFromArray() method didn't pass connections through filters and didn't pass edge data down for extensions to make use of if/when hooking in to add edge fields.

This PR standardizes ALL connections to make use of the AbstractConnectionResolver class, and with that, all Connection resolvers are also associated with a Loader.

Loaders for all Node Types

WPGraphQL makes use of a concept called "DataLoading". In a GraphQL request, many different types of resources can be asked for, and sometimes the same node can even be present multiple times in a response.

To make sure data is loaded as efficiently as possible, WPGraphQL first talks to the database for just a list of IDs for the resources, adds the IDs to an in-memory buffer, then circles back to resolve the objects by first checking if the objects have previously been resolved and cached, or by getting the objects from the database and storing them in the cache for future reference.

WPGraphQL abstracts quite a bit of the logic around this concept so that any new resource that wants to load data efficiently can create a new Loader extending the AbstractDataLoader class, and providing a loadKeys() method that loads objects as efficiently as possible given an array of IDs to load.

This pattern works even for data that's not in the WordPress database, for example registered post types and taxonomies, EnqueuedScripts, EnqueuedStylesheets, etc.

- This adds an `EnqueuedAsset` Interface, and an `EnqueuedScript` Type and `EnqueuedStylesheet` Type to the Schema.
- This adds connections from `ContentNode->EnqueuedScript`, `TermNode->EnqueuedScript` and `User->EnqueuedScript`
- This adds heaps of tests to make sure scripts can be queried in various contexts and work properly
- The model layer has been given some adjustments to make sure global state is setup properly during GraphQL Execution so that conditionals will work properly here
…p-graphql#1308-query-enqueued-assets-as-connections

# Conflicts:
#	src/Model/Post.php
#	src/Type/Object/PostObject.php
#	tests/wpunit/TypesTest.php
#	vendor/autoload.php
#	vendor/composer/autoload_commands_real.php
#	vendor/composer/autoload_framework_real.php
#	vendor/composer/autoload_real.php
#	vendor/composer/autoload_static.php
- Convert node resolver to use the loader and load_deferred method
- removed `taxonomyNames` field from ContentType type
- converted `connectedTaxonomies` field on ContentType to a formal connection
… other register_type methods `( $type_name, $config )`

- added phpstan (doesn't run during Github workflows yet, but can be used locally by developers to catch issues. Some need to be worked out still)
- updated `require_once` statements for plugin.php
@codecov
Copy link

codecov bot commented May 18, 2020

Codecov Report

Merging #1313 into develop will decrease coverage by 1.01%.
The diff coverage is 62.83%.

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #1313      +/-   ##
===========================================
- Coverage    63.62%   62.61%   -1.02%     
===========================================
  Files          166      181      +15     
  Lines         9540    10153     +613     
===========================================
+ Hits          6070     6357     +287     
- Misses        3470     3796     +326     
Impacted Files Coverage Δ
src/Data/PostObjectMutation.php 83.60% <ø> (-0.27%) ⬇️
src/Type/Object/ContentType.php 50.00% <0.00%> (+0.32%) ⬆️
src/Type/Union/CommentAuthorUnion.php 81.81% <ø> (ø)
src/Utils/Utils.php 69.69% <0.00%> (-22.31%) ⬇️
access-functions.php 71.18% <6.25%> (-24.27%) ⬇️
src/Type/Object/EnqueuedScript.php 33.33% <33.33%> (ø)
src/Type/Object/EnqueuedStylesheet.php 33.33% <33.33%> (ø)
src/Type/InterfaceType/EnqueuedAsset.php 37.77% <37.77%> (ø)
.../Data/Connection/ContentTypeConnectionResolver.php 48.93% <48.93%> (-51.07%) ⬇️
src/Model/Comment.php 69.84% <50.00%> (-1.35%) ⬇️
... and 76 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update bb78c71...b5f8098. Read the comment docs.

@jasonbahl jasonbahl self-assigned this May 19, 2020
@jasonbahl jasonbahl merged commit ec25aa2 into wp-graphql:develop May 19, 2020
@jasonbahl jasonbahl mentioned this pull request May 20, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant