Giter Site home page Giter Site logo

10up / headstartwp Goto Github PK

View Code? Open in Web Editor NEW
147.0 147.0 18.0 71.62 MB

Build a headless website fast with WordPress, the world’s most popular CMS, and Next.js, the most popular React framework. A free and open source solution by the experts at 10up.

Home Page: https://headstartwp.10up.com

TypeScript 45.30% JavaScript 9.24% Shell 0.04% PHP 6.69% CSS 38.57% Handlebars 0.11% EJS 0.04%

headstartwp's People

Contributors

antonio-laguna avatar darylldoyle avatar dependabot[bot] avatar desaiuditd avatar github-actions[bot] avatar jeffpaul avatar lcnogueira avatar nicholasio avatar orballo avatar tlovett1 avatar tobeycodes avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

headstartwp's Issues

When using create-next-app the .env should be ignored in .gitignore

It is useful that we have prepopulated the .env file so when people are using create-next-app the demo will work immediately but I would be worried that people will not remove this in the future and continue to commit this to git and potentially expose secrets.

I would suggest the readme documents creating the .env file or that we find a way to add it to the .gitignore and remove it from the initial commit created from create-next-app.

Data Fetching

The Data Fetching Strategy is a core piece of the headless framework and the foundation for streamlining the integration with WordPress. This issue describes the overall approach and idea for the implementation of Data Fetching within the 10up Next.js framework.

Design Goals

  • Doesn't require a custom WP plugin
  • Works with default REST API endpoint
  • Can be augmented with custom code and additional endpoints (e.g menus)
  • Supports Preview (to be worked in a separate task)
  • Hooks based
  • Supports Suspense (with the goal of being ready for Server Components which would allow us to do data fetching at the component level).
  • Typed with TypeScript, data fetching hooks would be fully typed making it easier to access wp objects.

Example Usage

// [[...args]].js
import { usePost } from '@10up/headless-core';

const Home = () => {
	const { data } = usePost();
	return <div>{data.post.title}</div>
};

export default Home;

export function async getServerSideProps(context) {
  const fetchStrategy = new SinglePostFetchStrategy();
  const data = await fetchStrategy.fetchFromServer(context);
  // maybe we have an helper function singlePostFetchFromServer(context);

  return {
     props: {
       // anything returned in data will automatically be injected in the custom hooks as initial data
       data,
     }
  };
}

It would automatically read from the URL for params matching WP permalinks. E.g: when visiting /hello-world usePost would use hello-world as the slug.

// category/[[...args]].js
import { usePost } from '@10up/headless-core';

const CategoryArchive= () => {
	const { data } = useCategory(); // or useTermArchive( { taxonomy: 'category' });
	return <div>{data.post.title}</div>
};

export default CategoryArchive;

List of custom Hooks

  • usePost: Fetch a single post
  • usePosts: Fetches an archive of posts
  • useSearch
  • useMenus

Implementation Brief
Because of how data fetching works in Next and until Server Components is stable, we need to provide an easy way to allow data fetching both at the component-level and through getServerSideProps and getStaticProps.

The proposed solution is to have an Abstract Class https://github.com/10up/headless/blob/380b280fee6968302e66fc430ebc83141ed0ca7d/packages/core/src/data/hooks/strategies/AbstractFetchStrategy.ts#L13 that exposes the necessary methods to be implemented by the different routes in the app.

The main method is
https://github.com/10up/headless/blob/380b280fee6968302e66fc430ebc83141ed0ca7d/packages/core/src/data/hooks/strategies/SinglePostFetchStrategy.ts#L10-L20 which implements obtaining the params supported from the URL. THose would include slug of post/category/term/taxonomy, pagination etc.

A base useFetch hook is already implemented that will help build specialized hooks for data fetching.
https://github.com/10up/headless/blob/380b280fee6968302e66fc430ebc83141ed0ca7d/packages/core/src/data/hooks/useFetch.ts#L8-L31

Previews

Is your enhancement related to a problem? Please describe.

Describe the solution you'd like

Designs

Describe alternatives you've considered

Additional context

Perfomance/Web Core Vitals

Is your enhancement related to a problem? Please describe.

Describe the solution you'd like

Designs

Describe alternatives you've considered

Additional context

WP Proxy

Is your enhancement related to a problem? Please describe.
Sitemaps, ads.txt, feed etc.

Describe the solution you'd like

Designs

Describe alternatives you've considered

Additional context

feat(data-fetching): add post-type option to usePost and usePosts

Is your enhancement related to a problem? Please describe.

Currently, there's no way to fetch custom post types.
Describe the solution you'd like

The ability to pass a post_type parameter to usePost and usePosts

e,g:

usePosts({postType: 'books'}).

If the endpoint name matches the postType slug then we can just assume the endpoint would be https://js1.10up.com/wp-json/wp/v2/books.

However, that is not always true. We would need a way to add config files where post types can be declared. See this for a reference/idea: https://tutorial.frontity.org/part6-custom-post-types/add-support-for-cpts

Designs

Describe alternatives you've considered

Additional context

usePosts: add information about current route

usePosts fetches posts for many routes like authors, categories, custom tax etc.

We need to include a object that describes which route the data is for like: isPosts, isArchive, isSearch etc.

Some params are not taken into account by `buildEndpointUrl`

Some params like category, tag, and any custom taxonomy will not be added to the endpointUrl which is used as a key storing the data in swr internal cache.

To reproduce simply call useFetchPosts or usePosts twice in the same component:

// This will fetch data properly.
useFetchPosts({
        category: 'test',
	per_page: 1,
});

// this will still return data for `category: 'test'`
useFetchPosts({
        category: 'test2',
	per_page: 1,
});

The workaround is to add dummy params to force the cache key to be different e.g:

useFetchPosts({
        category: 'test2',
	per_page: 1,
        dummy: 'random-string'
});

The reason for this bug is because current in https://github.com/10up/headless/blob/daaedce010afeffdb77cd32ba2819f47580e33a0/packages/core/src/data/strategies/PostsArchiveFetchStrategy.ts#L265-L270

A few parameters are removed and thus are not taken into account when the URL endpoint is created. Those attributes are only added in the fetch function but that is too late.

The simple fix is to have a separate method to create a cache key in order to prevent buildEndpointUrl from creating potentially invalid endpoint urls.

Transparent Post Rewrites

Describe the bug
Since #28, transparent post rewrites (i.e /post-name => /post/post-name) stopped working as I had to disable them otherwise next.config.js rewrites wouldn't work.

Steps to Reproduce

  1. Visit the '/cache-healthcheck route. It should return a "Ok" message.
  2. Uncomment the following line https://github.com/10up/headless/blob/0e62f9e743ce8fa7e1d46c874d536031aadc6ec5/packages/next/src/middlewares/appMidleware.ts#L60
  3. Visit the '/cache-healthcheck' route. It will return a 404. It should return a "Ok" message instead

Expected behavior
The expected behavior is that rewrites specified in next.config.js still works. If we uncomment the rewrite logic in our middleware it's completely skipping that as returning a rewrite response in the middleware seems to cause Next.js to completely ignore any other rewrites (which actually makes sense I think).

There are a few things we can consider:

  1. Figure out a way to have Next.js still check next.config.js rewrites after we rewrite the response in the middleware
  2. Manually check for next.config.js rewrites in our middleware.
  3. Completely remove the logic getRewriteRequest in the middleware. This would force the end use to chose what the default catch all route should be (either single article or post listing).

Screenshots

Environment information

  • Device:
  • OS:
  • Browser and version:

WordPress information

Site Health info:

Additional context

Gutenberg PoC

Is your enhancement related to a problem? Please describe.

Describe the solution you'd like

  • Parsing
  • API Replacing markup with React Components
  • Replace markup with default components (Image, Link)
  • Ship default GB styles

Designs

Describe alternatives you've considered

Additional context

Possible improvements when Headless Site Address Setting Not Entered

When loading the headless version of the site if you have not filled in Headless Site Address Setting you will get the following error in Next.js as the URL returns a 400 error.

FetchError: invalid json response body at https://site.test/clubpublix/wp-json/headless-wp/v1/app?_embed=true reason:

I spent some time debugging the code before I noticed there was a notice in WP-Admin. In hindsight it's obvious the WP plugin needs to know where the headless site lives but first time install I wasn't thinking about it. I would suggest a couple of improvements:

  1. Return a friendly JSON error in the REST API with the 400 status code
  2. Include installation instructions in the plugin readme

Routing/Templating

Is your enhancement related to a problem? Please describe.

Describe the solution you'd like

Designs

Describe alternatives you've considered

Additional context

CPTs and Custom Taxonomies

Is your enhancement related to a problem? Please describe.

Describe the solution you'd like

Designs

Describe alternatives you've considered

Additional context

Config File

Is your enhancement related to a problem? Please describe.

Describe the solution you'd like

There are a few config options for the framework that currently live in ENV variables. It would be best to have a dedicated file: something like headless.config.js where a single object is exported with various options.

The options would be:

  1. custom post types config (see #22 )
  2. custom taxonomies config (TBD)
  3. Redirect strategy (see https://github.com/10up/headless/blob/trunk/projects/wp-nextjs/.env#L2)
  4. etc

Ideally, this config would be accessible anywhere in the framework. We already have a settings provider and we can prob store all of these settings there https://github.com/10up/headless/tree/trunk/packages/core/src/provider

Designs

Describe alternatives you've considered

Additional context

Better error handling

When there is an error fetching data the framework always redirect to a 404 even if it isn't a 404.

style attribute should be passed to components of the block renderer

We should be able to fix this by delegating to BlocksRenderer the responsibility of parsing and forwarding the style attribute just like we do for children.

In BlocksRenderer, on the createElement call we should pass a style object as prop

https://github.com/10up/headless/blob/81c18cf446db0388583544213ef3efbfd9beaac1/packages/core/src/react/components/BlocksRenderer.tsx#L199-L204

But before passing the style prop we need to convert the string notation to object notation.

There's already a function that given a domNode returns the style in object notation
https://github.com/10up/headless/blob/81c18cf446db0388583544213ef3efbfd9beaac1/packages/core/src/react/blocks/utils.ts#L120

We'll also need to update the IBlock interface and add an optional style attribute
https://github.com/10up/headless/blob/81c18cf446db0388583544213ef3efbfd9beaac1/packages/core/src/react/components/BlocksRenderer.tsx#L58

Lastly, on every core block implementation, we'll need to to forward the style prop to Component.

E.g:

export function AudioBlock({ style, domNode: node, children, component: Component }: IAudioBlock) {
	const { name, className } = useBlock(node);
	const blockAttributes = useBlockAttributes(node);

	const audio = node.firstChild as Element;
	const figcaption = node.lastChild as Element;
	const caption = (figcaption.firstChild as Text).data;
	const audioAttributes = audio.attribs;

	return (
		<Component
			name={name}
			domNode={node}
			className={className}
			src={audioAttributes.src}
			caption={caption}
			autoplay={!!audioAttributes.autoplay}
			loop={!!audioAttributes.loop}
			preload={audioAttributes.preload}
			attributes={blockAttributes}
                       style={style}
		>
			{children}
		</Component>
	);
}

Shareable React Hooks

Shareable React Hooks

As we've discussed, I have a bunch of hooks that keep coming with me from a bunch of projects. I want to make them shareable and testable and hopefully in TS so we can move in that direction.

Here's a list of hooks and a brief explanation.

I have these hooks already done

useCurrentBreakpoint

Idea is to get this with a provider so it can be configured per project's need (with sensible defaults). This is helpful to trigger state changes on resizes that would need libraries updated settings (normally libraries that aren't React friendly)

useDebounce

Hook that debounces a value so it only causes re-render after it settled the debounce.

(Leverages useDebouncedCallback)

useDebouncedCallback

A hook that runs a callback debounced by a delay parameter so it will only run once within that time no matter the amount of times it's called.

(Leveraged by useDebounce)

useEffectAfterRender

Convenient helper hook to only run effect changes after the component has been rendered.

useEffectOnce

A hook that wraps useEffect to avoid needing to pass any dependency. The main reason is to ensure the intention of the developer is clear since these usages get flagged by eslint.

useEffectRef

A hook that's a safe wrapper around a ref. useEffect cant have useRef current as a watcher, and refs can be undefined and then set later, change to a different element or unmount and become undefined.

This is mostly a hook leveraged by other hooks when a ref is needed for a given element.

useEvent

A hook that listens to an event and deals with removing the listener when unmounted.

useExternalScript

Creates script tag for provided script to run. Uses cache to not allow multiple instances of script.

useFocusTrap

A hook that traps focus onto a given element upon receiving true and will return focus upon receiving false.

The hook will be pretty naive as there are lots of edge cases not covered given that they won't be likely run into. Those cases are for example (but not limited) handling disabled elements, non-normative tab-index, etc.

useIsMounted

A hook that gets a mounted property to true once it's mounted. This will only happen on the browser since servers don't mount components but merely renders. This hook is intended to be used with problematic effects that might happen due to differences between browser and server.

useIsTransitioning

Next specific hook that will provide a context and provides info for routeChangeStart and such events. Intended to mount/unmount elements on these events or show a loading indicator.

useKey

Convenient wrapper around useEvent to handle key presses allowing to send either the key being watched, a function
to evaluate or any truthy/falsy value

  • If it's a string it will be compared against the event's key property to see if it maches.
  • If it's a function it will be run with the event for the hook's usage to decide.
  • If it's any other type it'll evaluate falsy / truthy to decide.

useLazyContent

A hook that ties a value to the visibility of an element. If the browser doesn't support IntersectionObserver, it will just act as if it's intersecting.

useOnClickOutside

A hook that acts once you click outside of the given element. Useful to close modals

useOnScreenCallback

A hook that calls a callback once intersects with the intersection observer.

useOnWindowResize

Hook to be called when the window is resized.

usePrefersReducedMotion

A hook that checks whether the user has a system setting that prefer things to not have motion to avoid vestibular disorder related issues. Safe for SSR.

usePrevious

Holds a value onto a reference that gets updated after a new render happens. This can be used in combination with useState to store the previous value as well.

useSafeLayoutEffect

A hook that can be used when useLayoutEffect is wanted but avoid crashing on SSR.

useScroll

Custom hook to run an effect once the user scrolls through the page. Provides { prevPos, currPos } so it can be used to determine scroll direction.

useScrollLock

Custom hook that will lock the body's overflow to prevent scrolling if the given value is true or truthy.

Server-Managed Redirects

Is your enhancement related to a problem? Please describe.

Describe the solution you'd like

Designs

Describe alternatives you've considered

Additional context

Create a babel plugin that automatically filter's the page data sent back the page

Overview

In #38 A manual way of filtering the REST API response was introduced with the goal of reducing the HTML size of the rendered HTML page by reducing the amount of "props" sent back to the page.

The goal of this issue is to investigate & implement a babel plugin that would (via settings) do two things:

1 - Infer all of the fields being used from a given hook (e.g: usePosts).
2 - Pass the lists of fields being used to the appropriate fetchHookData call.

Note: The babel plugin should live in its own package, preferably written in plain node.js to avoid the need of transpilation.

Example

Consider the following next.js page route:

import {
	usePost,
	fetchHookData,
	addHookData,
	handleError,
	usePosts,
	useAppSettings,
} from '@10up/headless-next';
import { PageContent } from '../components/PageContent';
import { singleParams } from '../params';
import { resolveBatch } from '../utils/promises';

const SinglePostsPage = () => {
       // here the babel plugin should track properties that are accessed from `data`.
	const { loading, error, data } = usePost(singleParams);

	if (loading) {
		return 'Loading...';
	}

	if (error) {
		return 'error...';
	}

	return (
		<div>
			<h1>{data.post.title.rendered}</h1>
			<Blocks html={data.post.content.rendered} />
		</div>
	);
};

export default SinglePostsPage;

export async function getStaticProps(context) {
	try {
		// fetch batch of promises and throws errors selectively
		// passing `throw:false` will prevent errors from being thrown for that promise
		const settledPromises = await resolveBatch([
			{
				func: fetchHookData(usePost.fetcher(), context, { params: singleParams }),
			},
			{ func: fetchHookData(useAppSettings.fetcher(), context), throw: false },
		]);

		return addHookData(settledPromises, {});
	} catch (e) {
		return handleError(e, context);
	}
}

In the first step, the babel plugin should detect that the fields used from usePost are title and content. In the second step, the babel plugin should detect the fetchHookData call that is using the associated use post fetched function (usePost.fetcher()).

For this initial implementation let's only consider fields one level deep: e.g: if just post.content.rendered is being used, we can just "allow" the whole post.content object.

In the second step, the babel plugin would need to modify the code to include the allowed lists of fields. For the example above the modified code would look like this:

const settledPromises = await resolveBatch([
			{
				func: fetchHookData(usePost.fetcher(), context, { params: singleParams, filter: { method: 'ALLOW', fields: ['title', 'content']} }),
			},
			{ func: fetchHookData(useAppSettings.fetcher(), context), throw: false },
		]);

A couple of notes:

  • All of the built-in custom hooks follow the same interface for returning data. They always return data via the data object. All of the next.js custom hooks are defined in https://github.com/10up/headless/tree/develop/packages/next/src/data/hooks
  • Child component can access the data from the page route by just calling the hook, so this way it is possible to avoid having to pass props down to child components. For this reason, a minimum viable implementation of the babel plugin would also need to traverse child components.

Revelant Links
https://lihautan.com/step-by-step-guide-for-writing-a-babel-transformation/
https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md

Unable to active WP plugin when Frontity is active

Fatal error: Cannot declare class Firebase\JWT\BeforeValidException, because the name is already in use in /var/www/html/wp-content/plugins/tenup-headless-wp/includes/classes/php-jwt/BeforeValidException.php on line 0

The issue that causes this is both plugins share the JWT classes.

In most cases this won't be an issue since it's unlikely users are running frontends for both at the same time. In our case we will be transitioning between them and it is likely we will need to run old frontity sites while launching new headless sites.

Possible solutions:

  1. Check if the class has already been loaded and only load it if it hasn't. The downside to this is we rely on the class loaded first is a true copy for the class we need
  2. Use our own namespaced version of this class

Yoast Integreation

Is your enhancement related to a problem? Please describe.

Describe the solution you'd like

Designs

Describe alternatives you've considered

Additional context

Provide a simple (opt-in) state management solution

Is your enhancement related to a problem? Please describe.

The idea here is to provide an optional, built into the framework state management solution that could be used by project teams when they need to share data between components.

Describe the solution you'd like

I'd love to do a bit of an initial discovery here to come up with simple solutions. Here are my initial thoughts:

  • Redux is probably overkill
  • Context API might work well for simple use cases but might not scale very well.
  • Note that we don't want to store API data in the state, all of the data is to be handled by Next.js/useSWR. What I'm thinking here is UI state, things like Cart (for e-commerce websites), user auth state etc.

Let's do a bit of exploration and come up with a few suggestions.

I was thinking of the framework making it super easy to dispatch/action and access the shared state. Note that we should strive to only re-render components if the state of the components needs changes.

As an example, here's how Frontity handles this: https://tutorial.frontity.org/part3-displaying-posts/understanding-the-frontity-state

Designs

Describe alternatives you've considered

Additional context

Author Archives

Is your enhancement related to a problem? Please describe.

Describe the solution you'd like

Designs

Describe alternatives you've considered

Additional context

Internationalization Support

Next.js support internationalization but we should look into building a integration with WordPress that would easily allow the creation of a single next.js app with support to multiple languages.

This could work both with a multisite set up and a plugin like Polylang.

Component Library PoC

Is your enhancement related to a problem? Please describe.

Describe the solution you'd like

Designs

Describe alternatives you've considered

Additional context

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.