Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: @prismicio/vue refresh and Vue 3 support #46

Open
lihbr opened this issue Mar 10, 2021 · 2 comments
Open

RFC: @prismicio/vue refresh and Vue 3 support #46

lihbr opened this issue Mar 10, 2021 · 2 comments
Labels

Comments

@lihbr
Copy link
Member

@lihbr lihbr commented Mar 10, 2021

Status

🕵️‍♀️  Second implementation has been made and fully tested, the associated PR is awaiting for review, QA, and documentation. Vue 2 support is still hanging.

Overview

This request for comments (RFC) presents a new interface for the Vue plugin, but also components, and now composables, to make presenting and fetching Prismic content easy and extendable. Most of the interface described below is inspired by the existing @prismicio/vue library. Some enhancements and new features made possible by Vue 3 are new.

The proposals defined in this RFC are designed for all Vue.js users. Higher-level kits like @nuxtjs/prismic are meant to take advantage of this kit to provide more in-depth integration with their specific meta-framework.

Background Information

@prismicio/vue currently includes the following:

Plugin

Package's default export.

  • PrismicVue: Plugins to inject Prismic client, helpers, and components into Vue applications.

Client

Only injected by the plugin.

  • client: Provides access to a Prismic client instance inside the Vue application.

Helpers

Only injected by the plugin.

  • asHtml(): Serializes a Prismic rich text field to an HTML string;
  • asText() & richTextAsPlain: Serializers a Prismic rich text field to a plain text string;
  • asDate(): Transforms a Prismic date or timestamp field into a JavaScript Date object;
  • asLink(): Resolves a Prismic link field to a URL.

Components

Injected by the plugin and available through a dedicated export.

  • <prismic-embed />: Renders a Prismic embed field;
  • <prismic-image />: Renders a Prismic image field;
  • <prismic-rich-text />: Renders a Prismic rich text field;
  • <prismic-link />: Renders a Prismic link field.

While carrying a bit of legacy architecture and interface, the current @prismicio/vue kit is robust and working well with Vue 2. Reworking this kit will allow taking advantage of the new kits we've worked on (@prismicio/client & @prismicio/helpers), provide first-class TypeScript integration (with the new @prismicio/types package), and support for Vue 3.

Thanks to the knowledge we acquired supporting Vue at Prismic, this new version will also allow standardizing the interface offered by higher level Vue.js kits (@nuxtjs/prismic), making the development with Vue.js & Prismic consistent across meta-frameworks.

Proposal

The @prismicio/vue kit will still be architecture around a main Vue.js plugin. Alongside it, the following components and composables make for a robust Vue integration with Prismic:

  • <prismic-embed />: Renders a Prismic embed field;
  • <prismic-image />: Renders a Prismic image field;
  • <prismic-link />: Renders a Prismic link field;
  • <prismic-rich-text />: Renders a Prismic rich text field;
  • <prismic-text />: Renders a Prismic rich text field as text;
  • <slice-zone />: Renders Slices a Prismic Slice Zone;
  • usePrismicDocuments() & alternatives: Prismic document fetching composables.

The following are described below, each of them now being available through the package export.

Plugin

createPrismic()

This factory function makes for the only way to create a @prismicio/vue plugin instance to use with Vue.js. It supports the following options (simplified TypeScript interface):

interface PrismicPluginOptions {
	// `endpoint` is required to init the client:
	endpoint: string;
	clientConfig?: ClientConfig;

	// Alternatively to `endpoint` & `clientConfig`, a client instance can be directly passed:
	client: Client;

	// Shared options:
	linkResolver?: LinkResolverFunction;
	htmlSerializer?: HTMLFunctionSerializer | HTMLMapSerializer;
	injectComponents?: boolean; // defaults to `true`
	components?: {
		linkBlankTargetRelAttribute?: string;
		linkInternalComponent?: string | ConcreteComponent;
		linkExternalComponent?: string | ConcreteComponent;
		imageComponent?: string | ConcreteComponent;
		sliceZoneDefaultComponent?: string | ConcreteComponent;
	}
}

Installing the plugin then looks like this:

import { createApp } from "vue";

import App from "./App.vue";
import { createPrismic } from "@prismicio/vue";

const prismic = createPrismic({ endpoint: "repo-id-or-full-endpoint" });

createApp(App).use(prismic).mount("#app");

usePrismic()

This composable gives access to the API exposed by the plugin when using the composition API, it's an alternative to this.$prismic when using the option API:

import { usePrismic } from "@prismicio/client";

const prismic = usePrismic();

Injected Client

A Prismic client and necessary properties are injected by the plugin:

  • client: A @prismicio/client instance;
  • predicate: Query predicates from @prismicio/client;
  • cookie: Prismic cookies from @prismicio/client.

Those methods and properties are accessible through this with the options API and usePrismic() with the composition API:

// Options API
this.$prismic.client.getByUID(...);

// Composition API
const { client } = usePrismic();
client.getByUID(...);

Injected Helpers

Injected helpers now are the ones exported by @prismicio/helpers:

  • asText(): Serializers a Prismic rich text field to a plain text string;
  • asHTML() (now capitalized): Serializes a Prismic rich text field to an HTML string;
  • asLink(): Resolves a Prismic link field to a URL;
  • asDate(): Transforms a Prismic date or timestamp field into a JavaScript Date object;
  • documentToLinkField(): Converts a document into a link field;
  • documentAsLink(): Resolved a document to a URL.

asHTML, asLink, and documentAsLink will default to using the link resolver and HTML serializer exposed by the plugin if provided.

Those helpers are accessible through this with the options API and usePrismic() with the composition API:

// Options API
this.$prismic.asText(...);

// Composition API
const { asText } = usePrismic();
asText(...);

Injected Components

All components described in this proposal are injected and made available globally to the Vue application by the plugin by default. Injection can be disabled by turning the injectComponents plugin option to false, components will still take advantage of options provided through the components option object to the plugin though.

Components

Each of the following components can either be injected globally using the plugin (see above) or imported manually:

import { PrismicEmbed } from "@prismicio/vue";

<prismic-embed />

This component renders a Prismic embed field:

<prismic-embed :field="document.data.embed" />

The output will be wrapped by an HTML div tag by default. A custom wrapper tag, component, or functional component can be supplied using the wrapper prop:

<prismic-embed :field="document.data.embed" :wrapper="AppContainer" />

<prismic-image />

This component renders a Prismic image field:

<prismic-image :field="document.data.image" />

The image will be rendered using a standard img tag. A custom image component or functional component can be supplied directly or configured at the Prismic plugin level. Additional props can also be supplied directly to the component:

<prismic-image
	:field="document.data.image"
	:image-component="nuxt-img"
	:image-component-additional-props="{ preset: 'cover' }"
/>

<prismic-link />

This component renders a Prismic link field or document:

<prismic-link :field="document.data.link">Link</prismic-link>

The link or document will be resolved using the link resolver provided to the Prismic plugin if available, otherwise, a link resolver can be directly supplied:

<prismic-link
	:field="document.data.link"
	:link-resolver="(doc) => /* ... */"
>
	Link
</prismic-link>

The link will be rendered using Vue Router <router-link /> component if internal or HTML anchor tag if external. Custom HTML tags, components, or functional components can be supplied directly or configured at the Prismic plugin level to handle internal and external links as desired:

<prismic-link
	:field="document.data.link"
	external-component="MyAnchorTag"
	:internal-component="MyRouterLink"
>
	Link
</prismic-link>

target and rel attributes can be explicitly supplied to be rendered indifferently:

<prismic-link
	:field="document.data.link"
	target="_blank"
	rel="noopener noreferrer"
>
	Link
</prismic-link>

Links with blank target will have their rel attribute set to "noopener noreferrer". Another value can be supplied directly or configured at the Prismic plugin level:

<prismic-link
	:field="document.data.link"
	blank-target-rel-attribute="noopener"
>
	Link
</prismic-link>

A low level usePrismicLink composable is also exported:

import { usePrismicLink } from "@prismicio/vue";

const { type, href, target, rel } = usePrismicLink(/* options are similar to component's props */);

<prismic-rich-text />

This component renders a Prismic rich text or title field as HTML:

<prismic-rich-text :field="document.data.richText" />

To avoid issues like nuxt-community/prismic-module#60, this component now takes care of navigating internal links using Vue Router if available natively.

Links within the rich text field will be resolved using the link resolver provided to the Prismic plugin if available, otherwise, a link resolver can be directly supplied. Similarly, the rich text field will be serialized using the HTML serializer provided to the Prismic plugin if available, otherwise, an HTML serializer can also be directly supplied.

<prismic-rich-text
	:field="document.data.richText"
	:link-resolver="(doc) => /* ... */"
	:html-serializer="(type, node, text, children, key) => /* ... */"
/>

The output will be wrapped by an HTML div tag by default. A custom wrapper tag, component, or functional component can be supplied using the wrapper prop:

<prismic-rich-text :field="document.data.richText" :wrapper="AppContainer" />

A low level usePrismicRichText composable is also exported:

import { usePrismicRichText } from "@prismicio/vue";

const { html } = usePrismicRichText(/* options are similar to component's props */);

<prismic-text />

This component renders a Prismic rich text or title field as plain text:

<prismic-text :field="document.data.richText" />

The output will be wrapped by an HTML div tag by default. A custom wrapper tag, component, or functional component can be supplied using the wrapper prop:

<prismic-text :field="document.data.richText" :wrapper="AppContainer" />

Similar to the asText() function from @prismicio/helpers, a separator prop is also accepted and default to " " (a space):

<prismic-text :field="document.data.richText" separator="foo" />

A low level usePrismicText composable is also exported:

import { usePrismicText } from "@prismicio/vue";

const { text } = usePrismicText(/* options are similar to component's props */);

<slice-zone />

This component renders a Prismic Slice Zone:

<slice-zone :slices="document.data.slices" :components="components" />

Additional data can be made available to each rendered slices using the context prop:

<slice-zone :slices="document.data.slices" :components="components" :context="{ foo: 'bar' }" />

A default component will be rendered if a component mapping from the components prop cannot be found. This component can be overridden by providing one to the Prismic plugin or by supplying one directly:

<slice-zone :slices="document.data.slices" :components="components" :default-component="MyDefaultComponent" />

The output will not be wrapped by default. A custom wrapper tag, component, or functional component can be supplied using the wrapper prop:

<slice-zone :slices="document.data.slices" :components="components" :wrapper="AppContainer" />

Defining the components prop object can have some gotchas, therefore a getSliceZoneComponents() helper function is made available:

<template>
	<slice-zone :slices="document.data.slices" :components="components" />
</template>

<script setup>
import { defineAsyncComponent } from "vue";
import { getSliceZoneComponents } from "@prismicio/vue";
import Foo from "./Foo.vue";

const components = getSliceZoneComponents({
	// Standard, mapping slices of type `foo` to component `Foo`:
	foo: Foo,

	// Lazy-loading a slice component:
	bar: defineAsyncComponent(
		() => import("./Bar.vue"),
	),

	// Assuming "Baz" is registered globally:
	baz: "Baz",
});
</script>

getSliceZoneComponents() is a helper function for:

components = {
	// prevents component from being made reactive, therefore preventing performance issues
	foo: typeof Foo === "string" ? Foo : markRaw(Foo),
	/* ... */
};

In a similar fashion, defining slice props can be daunting, therefore a getSliceComponentProps() helper function is made available:

<template>
	<section><!-- ... --></section>
</template>

<script setup>
import { getSliceComponentProps } from "@prismicio/vue";

// Minimal usage:
defineProps(getSliceComponentProps());

// In order to have a visual reminder of which props are made available, a placeholder hint can be supplied:
defineProps(getSliceComponentProps(["slice", "index", "slices", "context"]));
</script>

getSliceComponentProps() is a helper function for:

props = {
	slice: {
		required: true,
	},
	index: {
		required: true,
	},
	slices: {
		required: true,
	},
	context: {
		required: true,
	},
};

usePrismicDocuments() & alternatives

A collection of Vue composables, including usePrismicDocuments(), usePrismicDocumentByID(), and usePrismicDocumentsByType(), queries the Prismic REST API for content from a Prismic repository.

The API mirrors that of the @prismicio/client library in composable form. It handles different states automatically, such as loading states and data persistence between re-renders. API parameters, such as lang and orderings, are provided as each composable's last parameter, just like using the client directly.

The following composables fetch one or more documents. They return Prismic's paginated API responses which can be helpful when displaying paginated content.

  • usePrismicDocuments()
  • usePrismicDocumentsByIDs()
  • usePrismicDocumentsByTag()
  • usePrismicDocumentsByTags()
  • usePrismicDocumentsByType()

The following list of composables is a variation of the previous list. These composables automatically fetch all documents from a paginated response and may make multiple network requests in order to fetch all matching documents.

  • useAllPrismicDocuments()
  • useAllPrismicDocumentsByIDs()
  • useAllPrismicDocumentsByTag()
  • useAllPrismicDocumentsByTags()
  • useAllPrismicDocumentsByType()

The following composables return one document.

  • usePrismicDocumentByID()
  • usePrismicDocumentByUID()
  • useFirstPrismicDocument(): Returns only the first matching document
  • useSinglePrismicDocument(): Returns a singleton document of a given type

All composables return the following data shape:

import { usePrismicDocumentByUID } from "@prismicio/vue";

const { state, data, error, refresh } = usePrismicDocumentByUID("page", "home");

Where:

  • state is the composable state from the PrismicClientComposableState export;
  • data is the API response depending on the composable or null when loading it;
  • error is the thrown error if in an error state;
  • refresh() is a function to perform again the composable's API request.

When using the Prismic plugin, each composable will use the plugin exposed @prismicio/client instance. A custom @prismicio/client instance can be provided as part of the composable's params object to override it. When not using the plugin it is mandatory to provide a @prismicio/client instance this way:

import { getEndpoint, createClient } from "@prismicio/client";
import { usePrismicDocumentByUID } from "@prismicio/vue";

const endpoint = getEndpoint("my-repo");
const client = createClient(endpoint);

const { state, data, error, refresh } = usePrismicDocumentByUID("page", "home", { client });

How to Provide Feedback

This is a public request for comments on the proposed ideas. If you have any feedback or suggestions, please feel free to reply below.

We would like to hear from potential users of these ideas whether the ideas positively or negatively impact workflows. If you have any additional ideas as well, please share them here.

Everything posted here is open for feedback and is not final. Development may have already begun by the time you are reading this, but please do not let that stop you from providing feedback.

Thank you!

History

Outdated - First iteration based on old kits

🎉 Vue 3 Kit RFC 🎉

📣  Vue 3 Kit Suggestion Thread📖  Vue 2 Kit Reference

We're about to start the work on the next major version of @prismicio/vue (this repository). The main goal of this version will be to support Vue 3. A similar process will follow soon to support Nuxt 3, on our Nuxt kit.

RFC Process

This RFC will be used as a specification to know what needs to be implemented. It's a place for everyone to:

  • Know what's planned for the next version of @prismicio/vue;
  • Discuss, react, and share opinions on it.

This RFC is a collaborative and iterative process. We will do our best to take into account everyone's opinion about it. A related Suggestion Thread has also been created to discuss the present and future of this kit.

Specification

As stated in the title of the issue, this RFC is in a draft state. We're actively working on it and defining things thanks to user feedback we got and are getting!

Interface

Stay tuned! We're still in the process of defining a base interface for the next version of this kit. In the meantime, you can expect current components and helpers to remain similar in the next version.

TypeScript

The next version of @prismicio/vue will be written in TypeScript. This will allow us to provide a more robust kit while also providing a better developer experience for:

  • JavaScript and TypeScript developers, by having better IntelliSense in their projects;
  • TypeScript developers, by providing better type support of Prismic kits.

This change is motivated by an increasing number of requests around TypeScript for Prismic kits. At Prismic one of our goals this year is to improve TypeScript support across all our kits.

Q&A

Will the next major version still support Vue 2?

We're aiming to make this kit still compatible with Vue 2. However, to take advantage of the latest and greatest coming with Vue 3, it's more than possible that Vue 2 support will be available through a compatibility, eventually feature-restrained, build. You can think of it in this fashion:

import PrismicVue from "@prismicio/vue"; // Vue 3
import PrismicVue from "@prismicio/vue/2"; // Vue 2 compatible kit

Closing #5, #41


Please be respectful and friendly to everyone's ideas and point of view. Thanks for contributing! 💙

@lihbr
Copy link
Member Author

@lihbr lihbr commented Apr 9, 2021

Outdated - First alpha announcement based on old kits

Hey everyone, I'm happy to announce that we released an alpha reflecting our first sprint of work on this kit 🎉

Few Highlights

  • 🈂️  The kit has been completely rewritten using TypeScript, now providing great typing support;
  • 🖥️  Better IDE support with included Vetur/VueDX configuration;
  • 🌲  We focused on having a kit working for Vue 3 for now. The overall interface is quite similar to the Vue 2 (current) version of this kit while still providing some enhancement;
  • ⚠️  This version is not compatible with Vue 2 yet. We still need to ponder our thoughts on what strategy to adopt to provide an updated kit for both Vue 2 & 3. We're more than happy to discuss everyone's input on that;
  • 💥  Bear in mind that this is an alpha, that is may not be fully tested (actually we still need to implement tests with Jest!), and that new breaking changes might be introduced;
  • 📚  We also published a mini documentation website about how to use this alpha, check it out!

Try the Alpha Now

yarn add @prismicio/vue@alpha
# or with npm
npm install @prismicio/vue@alpha

RFC Status

The above RFC will be updated soon with more details. Right now one of our top questions is related to the plugin's configuration interface:

interface PrismicPluginOptions {
  endpoint: string;
  apiOptions?: ApiOptions;

  linkResolver?: LinkResolver;
  htmlSerializer?: HtmlSerializer<string>;

  client?: boolean | ClientOptions;
  dom?: boolean | DOMOptions;
  components?: boolean | ComponentsOptions;
}

And whether or not it makes sense to move the endpoint and apiOptions key inside the ClientOptions interface since those, while being "core", are part of the client kit, which may be disabled.

Source Code

Source code is available on the v3 branch.

As always we're more than happy to discuss the ongoing work on the Vue.js kit with you all!

Cheers!

@lihbr lihbr changed the title [RFC] [DRAFT] 🎉 Vue 3 Kit RFC 🎉 RFC: @prismicio/vue refresh and Vue 3 support Aug 20, 2021
@lihbr lihbr added v3 and removed help wanted labels Aug 20, 2021
@lihbr lihbr mentioned this issue Aug 20, 2021
@lihbr
Copy link
Member Author

@lihbr lihbr commented Aug 23, 2021

Update 08/23/2021: RFC has been updated reflecting 2nd implementation details.

One thing I'm unsure is about the naming of <slice-zone /> helper functions:

  • getSliceZoneComponents(): helps with some gotchas related to defining the components prop object (internally prevents components to be made reactive using markRaw);
  • getSliceComponentProps(): gets the verbose Vue.js props object that a slice has to implement.

Maybe having the latter named getSliceProps() might make it less confusing 🤔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
1 participant