wpengine / faustjs Goto Github PK
View Code? Open in Web Editor NEWFaust.js™ - The Headless WordPress Framework
Home Page: https://faustjs.org
License: Other
Faust.js™ - The Headless WordPress Framework
Home Page: https://faustjs.org
License: Other
NOTE: This is still a work in progress, feedback welcome.
Currently, @wpengine/headless
provides templating functionality for use in Next.js. This includes the templates in wp-templates/
, getNextStaticProps()
, getNextStaticPaths()
, etc.
With the improvements in #232, it will become much easier to query and fetch data.
With these improvements, we may want to consider leveraging the native Next.js page routing system, where the user would implement the data fetching functionality and getStaticProps
/getStaticPaths
themselves.
An example Next.js app using this format will look like:
pages/
├─ preview/ <- All pages under here are CSR/SSR only
│ ├─ post/
│ │ ├─ [[...postPreviewUri]].tsx
│ ├─ page/
│ │ ├─ [[...pagePreviewUri]].tsx
├─ posts/
│ ├─ index.tsx
│ ├─ [postSlug].tsx <-- /posts/hello-world
│ ├─ page/
│ │ ├─ [paginationTerm]/
│ │ │ ├─ [postCursor].tsx <-- posts/page/[before|after]/tbsda==
├─ category/
│ ├─ [categorySlug].tsx <-- category/featured
│ ├─ page/
│ │ │ ├─ [paginationTerm]/
│ │ │ | ├─[categoryCursor].tsx <-- category/featured/[before|after]/tbsda==
├─ _app.tsx
├─ 404.tsx
├─ index.tsx
├─ about.tsx
├─ [...pageUri].tsx
If necessary adjust exports to be accessible, and modify index.ts
files to export the relevant package exports.
We switched from Cypress to Codeception and still have some Cypress code in the repo. The Cypress code should be removed so as to not confuse newcomers to the repo.
Remove the re-exports from higher-level headless libs
Instead of <html>
, it should be <html lang="en-US">
. Next.js makes this doable by adding the i18n config to next.config.js file: https://nextjs.org/docs/advanced-features/i18n-routing#getting-started
We need basic contribution guidelines for the repo. For not let's borrow from TypeScript and also mention semantic commits
This is a reference to #198 which discusses:
- Authentication and authorization
- OAuth handlers
- JWT Auth
There is currently a limited implementation of auth in core
and the WordPress plugin. Here is how OAuth should work, including suggestions for how core
should handle authorization and authentication:
NOTE: Some of these features may already exist
WordPress plugin
should publish a route where you can send a user to have them authenticate with WordPressWordPress plugin
will generate a short-lived Authorization Code and redirect the user to a specified redirect URL on the frontendfrontend server
should use the Authorization Code and the configured secret key to exchange the code for an access token and refresh token (and associated expirations)frontend server
should store the access token and refresh token in a client-side cookie to be used with subsequent requests to WordPressWordPress plugin
should provide a WPGraphQL mutation that allows exchanging an Access Token and Refresh Token for a new Access Token and Refresh TokenWordPress plugin
should provide a WPGraphQL mutation that allows exchanging a username and password for an Access Token and Refresh Tokencore
should handle access token expiration by automatically requesting a new set of tokens when the access token is soon to expirecore
should provide functionality for:
Currently the WordPress plugin
only generates an access token with no expiration. We should move to generating both an access token and a refresh token. Here are my suggestions for default token expirations:
NOTE: These expirations should be configurable in the WordPress plugin
Currently the WordPress plugin
automatically generates a secret key using wp_generate_uuid4
. You are also able to regenerate the key when needed. Should this functionality remain the same? Some plugins (such as WPGraphQL JWT Auth) seem to require a developer to define a secret, and provide no menu functionality to automatically generate one. The benefit of doing it the way JWT Auth does it is you don't run the risk of a publisher accessing the menu and accidentally regenerating the key.
The headless-framework readme has a bad link down in the Guides section which takes the user to https://github.com/wpengine/headless-framework/blob/canary/docs/getting-started/README.mid
Changing the url to README.md takes you to the correct page.
These libraries are now split out, but need to be published to npm, and then react
and next
should be using the version of their local dependencies from NPM rather than through lerna. This will save us headache related to the multiple react versions, etc.
I suggest we have a canary
dist-tag that each of the packages use when specifying internal dependencies.
@matt-landers: We also do not have a name for the framework as of yet, so should we publish under @wpengine/headless-<package>
for now? i.e:
@wpengine/headless-core
@wpengine/headless-react
@wpengine/headless-next
This needs to be investigated. NPM 7 works fine, but there appear to be runtime issues with NPM 6. If necessary we can specify the minimum NPM version in the package.json
Currently, in the next
package, we are importing modules from @wpengine/headless-core
and @wpengine/headless-react
, then exporting them to be consumed in @wpengine/headless-next
. This allows us to only have a dependency of @wpengine/headless-next
when building Next.js applications:
import { authorizeHandler } from '@wpengine/headless-next'
However, as we add more functionality, this may start to pollute the named export.
We could use a submodule approach where we export our needed modules under a core
or react
submodule, then import them like so in Next.js:
import { authorizeHandler } from '@wpengine/headless-next/core'
import { HeadlessProvider } from '@wpengine/headless-next/react'
I think this approach helps delineate between modules better.
@matt-landers @wjohnsto Any thoughts on this?
I came across this and think it might be useful for the framework to support a number of data fetching scenarios. Let's look into it and see how it works https://github.com/gqless/gqless
Pay specific attention to:
packages/core
, packages/react
, and packages/next
can provide specific to Headless WordPress?Comment on this issue with any interesting findings. Try to include some sample code with your comments.
UPDATE: We're moving to gqless. Here is how it will work:
core
will ship with a default implementation of the gqless client and WPGraphQL schema.core
will publish a function for creating the client that allows overwriting of the schema with a custom version (supports custom WPGraphQL queries)core
will also allow intercepting fetch calls to handle the request, apply headers, etc.react
will use @gqless/react
and provide a similar level of configuration for the React gqless client as core
Branch for this here: https://github.com/wpengine/headless-framework/tree/feature-gqless
Right now we export type declarations from core, but if you import from /react
or /next
only in your file you will not get typings for any function calls.
The first link, "Enabling WordPress post previews" is a broken link in the Learn more section of the Getting Started page.
I think it's supposed to go to https://github.com/wpengine/headless-framework/tree/canary/docs/previews.
There is a small bug in the headlessConfig()
setter that is causing the provided config to not be saved.
This issue is in response to @wjohnsto's comment on #194
We need to break the package up with the first package being core
which should include (but not limited to):
The core package includes:
If a static homepage is set in Settings → Reading, the “latest posts” section of front-page.tsx
shows no posts. You have to set “Your homepage displays” to “latest posts” before posts appear.
Latest Posts should display posts whether “latest posts” or a static page is selected in reading settings.
npm run bootstrap && npm run dev
The Latest Posts section is empty.
Introduced in d56a5e7. If I check out the previous commit (d513e0e) all is well.
Logging console.log(pageProps.__APOLLO_STATE__.ROOT_QUERY);
in examples/getting-started/pages/_app.tsx
shows that nodes are empty for the root query when a static page is set:
getting-started: [Object: null prototype] {
getting-started: __typename: 'Query',
getting-started: 'nodeByUri({"uri":"/"})': { __ref: 'Page:cG9zdDo2' },
getting-started: generalSettings: [Object: null prototype] {
getting-started: __typename: 'GeneralSettings',
getting-started: title: 'fresh install',
getting-started: description: 'Just another WordPress site',
getting-started: url: 'http://localhost:3000'
getting-started: },
getting-started: 'contentNode({"asPreview":false,"id":"/","idType":"URI"})': { __ref: 'Page:cG9zdDo2' }
getting-started: }
There should be a nodes
property like this:
getting-started: [Object: null prototype] {
getting-started: __typename: 'Query',
getting-started: 'nodeByUri({"uri":"/"})': { __ref: 'ContentType:cG9zdF90eXBlOnBvc3Q=' },
getting-started: generalSettings: [Object: null prototype] {
getting-started: __typename: 'GeneralSettings',
getting-started: title: 'fresh install',
getting-started: description: 'Just another WordPress site',
getting-started: url: 'http://localhost:3000'
getting-started: },
getting-started: 'posts({})': [Object: null prototype] {
getting-started: __typename: 'RootQueryToPostConnection',
getting-started: pageInfo: [Object: null prototype] {
getting-started: __typename: 'WPPageInfo',
getting-started: endCursor: 'YXJyYXljb25uZWN0aW9uOjE=',
getting-started: hasNextPage: false,
getting-started: hasPreviousPage: false,
getting-started: startCursor: 'YXJyYXljb25uZWN0aW9uOjEw'
getting-started: },
getting-started: nodes: [ [Object], [Object] ]
getting-started: }
We no longer need lerna now that we have moved to using canary
dependencies. It can be removed and replaced with top-level scripts for cleaning and installing dependencies.
We need a suite of tests for the basic functionality of the react
package. This may include:
HeadlessProvider
Hi @nickcernis,
I was going through the tutorial to spin up an example headless site and ran into an issue with configuring the .env.local file. It appears that the secret variable has recently been changed to WP_HEADLESS_SECRET but when I try to run the project, it complains saying that "Error: WPE_HEADLESS_SECRET environment variable is not set.". Changing the variable name to WPE_HEADLESS_SECRET seems to fix the problem.
To reproduce this issue, ran the following command to start the project:
npx create-next-app -e https://github.com/wpengine/headless-framework/tree/canary --example-path examples/getting-started --use-npm
I then changed into that directory, renamed .env.local.sample to .env.local and then updated my settings, then ran npm run dev
to launch the project.
Kind regards
Anton
We need a suite of tests for the basic functionality of the next
package. This may include:
nextConfig
getNextStaticPaths
Remove ./nextConfig.js
and the next-transpile-modules
dependency from packages/next
.
We need an example project that uses the new broken out packages. Let's create an /examples/next/getting-started
directory and project. We can start with a simple blog site with SSG, previews, etc. It might make sense to copy the current getting-started and pair it down to only the features preserved in the new framework, but whoever owns this issue can make that decision.
Follow outline in #270 and make sure posts are working with the new canary framework. Log bug issues as-needed.
Things to make sure are supported:
Let's log new issues for bugs as they come up.
You should be able to move all the directories except public
into src
and then modify baseUrl
to be ./src
.
The post_link
filter currently in place only applies to posts: https://github.com/wpengine/headless-framework/blob/e5fb7f8b8f6902c7bf769dcee61ed172684ed352/plugins/wpe-headless/includes/replacement/callbacks.php#L152
A page_link
filter should also be added for similar URL replacement on pages: https://developer.wordpress.org/reference/hooks/page_link/
Follow outline in #270 and make sure categories are working with the new canary framework. Log bug issues as-needed.
By default, Next sets max-age=0
on the cache-control
headers. This is not ideal in a production app. The headers can be manipulated via next.config.js
https://nextjs.org/docs/api-reference/next.config.js/headers.
The framework should set a reasonable default and allow for some level of configuration.
I really liked this framework and i would like to use this one for my next project.
I am struggling with handle archive, category pages.
So how i can use these template files inside theme directory and also if i want to custom-page-template then what to do here?
Support for previewing drafts was merged in and works great. But only if you have the NEXT_PUBLIC_WORDPRESS_URL
config variable set instead of WORDPRESS_URL
. That means I have to have the whole site make client side requests in order to have draft previewing capabilities. It would be great if draft previewing worked without having to update the config variable.
Hey guys!
Killer framework, love using it so far. I'm running into an issue using the headless-framework with Local.
Local will open URLs with a cache-buster
query string to force browsers to load a fresh copy of the site. When Local opens the headless framework with that query string we get a 404. Removing the cache-buster query string allows the requested page to load.
I'm wondering if we can either whitelist the cache-buster
query string, or ignore all query strings in the routing.
Thanks!
Our tsconfig.json
's rootDir
is src
. Our ESLint config uses our tsconfig.json
for a parser config, so all files outside of src
are throwing an eslint warning in the ESLint VSC plugin.
We can add an ignore patterns for js/ts files outside of src
to fix this.
Since this is a monorepo we should support better instrumentation at the root level. Someone should not be required to scope their IDE to a particular directory in order to get the right level of tooling. We'll start by allowing linting and testing from the top level.
In an effort to clearly delineate what dependencies are allowed at different levels of the framework (e.g. core, react, next), I propose that we break the framework into smaller npm packages that depend on each other. The foundational package (core) should not have a dependency on react or next. This part of the framework should be able to be used by any frontend framework including Vue, Angular, Svelte, etc.
The core package includes:
The react package includes:
The next package includes:
This list is not exhaustive of what we should do, but it includes what is in the framework today with some future enhancements that we know we need to address.
This issue is in response to @wjohnsto's comment on #194
In order to create a clear delineation of dependencies, a react
package should be created with react specific features. Do not mix Next features in the react
package. This package should include (but not limited to):
wp plugin install --activate https://github.com/wp-graphql/wp-graphql-jwt-authentication/archive/refs/heads/develop.zip
Caused by this logic in the wp-graph-ql-jwt-authentication plugin (the validate_token()
call returns an error because the passed headless framework auth key doesn't match wpgraph-ql-jwt-authentication's expected keys):
add_action( 'init_graphql_request', function() {
$jwt_secret = Auth::get_secret_key();
if ( empty( $jwt_secret ) || 'graphql-jwt-auth' === $jwt_secret ) {
throw new \Exception( __( 'You must define the GraphQL JWT Auth secret to use the WPGraphQL JWT Authentication plugin.', 'graphql-jwt-auth' ) );
} else {
$token = Auth::validate_token();
if ( is_wp_error( $token ) ) {
add_action( 'graphql_before_resolve_field', function() use ( $token ) {
throw new \Exception( $token->get_error_code() . ' | ' . $token->get_error_message() );
}, 1 );
}
}
} );
Commenting that out allows previews to work again.
If we can't work around this in our plugin. wpgraph-ql-jwt-authentication may need to be adjusted.
cc @wjohnsto for visibility. This is a separate issue to #189 that came up while testing the fix in #190.
When you have the Enable public route redirects
flag enabled you are supposed to get 302 redirects for non-admin WP URLs to your specified frontend URL. This works and preserves the URL path (e.g. "http://mysite.local/foo/bar" will redirect to "http://localhost:3000/foo/bar"), but doesn't factor into the query string.
Steps to reproduce:
Enable public route redirects
is checkedPreviews will only work if your post links are from the root of your site (e.g. mysite.local/hello-world
) rather than at a separate path (e.g. mysite.local/blog/hello-world
).
I believe this is an issue with the plugin. The plugin is aware of and creates direct URLs to where it assumes the frontend has the preview handler API router. So if your "Front-end site URL" in your headless settings is https://mysite.local/blog
then the plugin will end up sending you to https://mysite.local/blog/api/auth/wpe-headless?redirect_url=blog%2Fhello-world%2Fpreview%2F21
if you are trying to preview an autosave, and https://mysite.local/blog/api/auth/wpe-headless?redirect_url=draft%2Fpreview%2F21
if you are trying to preview a draft post. There are a few things wrong with this,:
/blog/api/...
. In fact, in its current state the frontend is not able to specify a custom API route for the preview handler.
next.config.js
rewrites() {
return [
{
source: '/blog/api/:path*',
destination: '/api/:path*',
},
];
},
draft/preview/21
. This won't work, as the URL would need to be blog/draft/preview/21
.There are a couple ways you could deal with this issue, but before I get into that it is important to understand the reason the WP plugin is currently using direct URLs to the API route in the first place rather than rewriting to be the same URL WP would use. When using a WP site to preview drafts/autosaves the URL will contain preview information in the query string. In a Next app, you do not have access to query parameters in the GetStaticPropsContext
(see an issue about it here). So in order to support previewing with Next and getStaticProps
it is necessary to rewrite the URL query parameters to be part of the path.
With that being said, I think some day in the future Next may support query params in GetStaticPropsContext
and when that day comes we should revert back to simply using the same WP frontend URLs for previewing, which would in-effect decouple the plugin from the framework. For now, we have to come up with some way to do it that supports custom frontend routes for blog posts. Here is my suggestion:
https://mysite.local/
)/blog/
), which defaults to /
wp-admin
and want to preview a post, the URL will be rewritten to use the same schema that WP uses, only we will append /preview/
to the front of the path (e.g. https://mysite.local/preview/?p=21&preview=true
or https://mysite.local/preview/hello-world/?preview_id=1&preview_nonce=ac879988a0&preview=true
)./pages/preview/[[...page]].tsx
that will serve as their page for previews.
getServerSideProps
, but shouldn't use getStaticProps
I think doing this or something similar makes the most sense for now and will decouple the plugin from the frontend as much as possible while we wait for Next to add support for query params in GetStaticPropsContext
.
Thoughts?
Hello! I'm just walking by and seeing you start a project for PHP 5.6.
What is the reason?
That 11.9%? https://wordpress.org/about/stats/#php_versions
With the WPE headless plugin active, JWT authenticated requests give an authentication error.
Install the wp-graphql-jwt-authentication plugin:
wp plugin install --activate https://github.com/wp-graphql/wp-graphql-jwt-authentication/archive/refs/heads/develop.zip
Edit your wp-config.php
and add define( 'GRAPHQL_JWT_AUTH_SECRET_KEY', 'your-secret-token' );
Obtain an auth token for your user by running this POST GraphQL request in Postman:
mutation LoginUser {
login( input: {
clientMutationId: "uniqueId",
username: "your_login",
password: "your password"
} ) {
authToken
user {
id
name
}
}
}
Copy the "authToken" value from the response (without the surrounding quotes).
Make a new request in Postman for data that requires authentication, setting “Type” on the “Authentication” tab to “Bearer Token” and pasting your authToken into the token field.
{
generalSettings {
email
}
}
You will see an authentication error like this one:
{
"errors": [
{
"debugMessage": "Authentication Error",
"message": "Internal server error",
"extensions": {
"category": "internal"
},
"trace": [
{
"file": "/Users/nick/Sites/dev/wp-content/plugins/wp-graphql/src/Request.php",
"line": 604,
"call": "WPGraphQL\\Request::after_execute(instance of GraphQL\\Executor\\ExecutionResult)"
},
{
"file": "/Users/nick/Sites/dev/wp-content/plugins/wp-graphql/src/Router.php",
"line": 465,
"call": "WPGraphQL\\Request::execute_http()"
},
{
"file": "/Users/nick/Sites/dev/wp-content/plugins/wp-graphql/src/Router.php",
"line": 270,
"call": "WPGraphQL\\Router::process_http_request()"
},
{
"file": "/Users/nick/Sites/dev/wp-includes/class-wp-hook.php",
"line": 292,
"call": "WPGraphQL\\Router::resolve_http_request(instance of WP)"
},
{
"file": "/Users/nick/Sites/dev/wp-includes/class-wp-hook.php",
"line": 316,
"call": "WP_Hook::apply_filters(null, array(1))"
},
{
"file": "/Users/nick/Sites/dev/wp-includes/plugin.php",
"line": 551,
"call": "WP_Hook::do_action(array(1))"
},
{
"file": "/Users/nick/Sites/dev/wp-includes/class-wp.php",
"line": 388,
"function": "do_action_ref_array('parse_request', array(1))"
},
{
"file": "/Users/nick/Sites/dev/wp-includes/class-wp.php",
"line": 750,
"call": "WP::parse_request((empty string))"
},
{
"file": "/Users/nick/Sites/dev/wp-includes/functions.php",
"line": 1291,
"call": "WP::main((empty string))"
},
{
"file": "/Users/nick/Sites/dev/wp-blog-header.php",
"line": 16,
"function": "wp()"
},
{
"file": "/Users/nick/Sites/dev/index.php",
"line": 17,
"function": "require('/Users/nick/Sites/dev/wp-blog-header.php')"
},
{
"file": "/Users/nick/.composer/vendor/laravel/valet/server.php",
"line": 219,
"function": "require('/Users/nick/Sites/dev/index.php')"
}
]
}
]
}
After wp plugin deactivate wpe-headless
, the same request yields:
{
"data": {
"generalSettings": {
"email": "[email protected]"
}
},
"extensions": {
"debug": []
}
}
The default authentication token expiry is 300 seconds (5 minutes). You can extend this with PHP for testing purposes (not recommended in production):
<?php
// Extend JWT token expiration from 300 seconds to 1 day.
function custom_jwt_expiration( $expiration ) {
return 86400;
}
add_filter( 'graphql_jwt_auth_expire', 'custom_jwt_expiration' );
Follow outline in #270 and make sure custom pages are working with the new canary framework. Log bug issues as-needed.
NOTE: This is still a work in progress, I will add to it as more ideas come to mind
Questions unanswered:
This is a reference to #198 which discusses:
- Data fetching
- Retrieve WP data based on the URL scheme
- Fetch custom queries
- Compose queries from fragments so that components can let the framework know the data it requires
- Provide a mechanism for mutations
Currently @wpengine/headless
has the following services:
getPosts
: get a list of postsgetContentNode
: get an individual post or pagegetGeneralSettings
: get the WP site settingsgetUriInfo
: get information about a URL using WPGraphQL's nodeByUri
querygetContentNode
Something that can get confusing is how getContentNode
will work for posts or pages. We probably want two functions, one for getting a post and another for getting a page. This will simplify our queries and reduce some confusion:
getPost
: gets a single postgetPage
: gets a single pageWe should provide functionality around the WPGraphQL categories
query:
getCategories
: get a list of categoriesgetCategory
: get a single categorygetPostsByCategory
: gets a list of posts for a specific categoryWe should provide functionality around the WPGraphQL tags
query:
getTags
: get a list of tagsgetTag
: get a single taggetPostsWithTags
: gets a list of posts that contain specified tagsWe should provide functionality around the WPGraphQL menus
query:
getMenus
: get a list of menusgetMenu
: get a single menuUpdate the @wpengine/headless-{core,react,next}
changelogs to reflect the initial release (0.6.4
) and version 0.6.5
Follow outline in #270 and make sure previews are working with the new canary framework. Log bug issues as-needed.
Hey All, I just ran across a "bug" and I'm not sure who needs to know so thought I'd send it to both of you. So yesterday I changed the "Site Address (URL)" to point to my atlas front end. this fixed all the "View" buttons for posts and pages. It fixes an issue in wpgraphql I talked with Jason on the other day where the nodeByUri
uses the site_url
.
HOWEVER, once this is changed Gutenberg is broken. For some reason Gutenberg uses the frontend url and it breaks the ability to create/update posts. There are some solutions @ WordPress/gutenberg#1761 .
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.