feat(live-preview-vue): new live-preview-vue package (#5933)
Co-authored-by: Christian Gil <mrcgam.christian@gmail.com>
This commit is contained in:
@@ -8,7 +8,7 @@ keywords: live preview, frontend, react, next.js, vue, nuxt.js, svelte, hook, us
|
||||
|
||||
While using Live Preview, the Admin panel emits a new `window.postMessage` event every time a change is made to the document. Your front-end application can listen for these events and re-render accordingly.
|
||||
|
||||
Wiring your front-end into Live Preview is easy. If your front-end application is built with React or Next.js, use the [`useLivePreview`](#react) React hook that Payload provides. In the future, all other major frameworks like Vue, Svelte, etc will be officially supported. If you are using any of these frameworks today, you can still integrate with Live Preview yourself using the underlying tooling that Payload provides. See [building your own hook](#building-your-own-hook) for more information.
|
||||
Wiring your front-end into Live Preview is easy. If your front-end application is built with React, Next.js, Vue or Nuxt.js, use the `useLivePreview` hook that Payload provides. In the future, all other major frameworks like Svelte will be officially supported. If you are using any of these frameworks today, you can still integrate with Live Preview yourself using the underlying tooling that Payload provides. See [building your own hook](#building-your-own-hook) for more information.
|
||||
|
||||
By default, all hooks accept the following args:
|
||||
|
||||
@@ -36,6 +36,10 @@ And return the following values:
|
||||
For example, `data?.relatedPosts?.[0]?.title`.
|
||||
</Banner>
|
||||
|
||||
<Banner type="info">
|
||||
It is important that the `depth` argument matches exactly with the depth of your initial page request. The depth property is used to populated relationships and uploads beyond their IDs. See [Depth](../getting-started/concepts#depth) for more information.
|
||||
</Banner>
|
||||
|
||||
### React
|
||||
|
||||
If your front-end application is built with React or Next.js, you can use the `useLivePreview` hook that Payload provides.
|
||||
@@ -71,11 +75,40 @@ export const PageClient: React.FC<{
|
||||
}
|
||||
```
|
||||
|
||||
<Banner type="info">
|
||||
If is important that the `depth` argument matches exactly with the depth of your initial page
|
||||
request. The depth property is used to populated relationships and uploads beyond their IDs. See
|
||||
[Depth](../getting-started/concepts#depth) for more information.
|
||||
</Banner>
|
||||
### Vue
|
||||
|
||||
If your front-end application is built with Vue 3 or Nuxt 3, you can use the `useLivePreview` composable that Payload provides.
|
||||
|
||||
First, install the `@payloadcms/live-preview-vue` package:
|
||||
|
||||
```bash
|
||||
npm install @payloadcms/live-preview-vue
|
||||
```
|
||||
|
||||
Then, use the `useLivePreview` hook in your Vue component:
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { PageData } from '~/types';
|
||||
import { defineProps } from 'vue';
|
||||
import { useLivePreview } from '@payloadcms/live-preview-vue';
|
||||
|
||||
// Fetch the initial data on the parent component or using async state
|
||||
const props = defineProps<{ initialData: PageData }>();
|
||||
|
||||
// The hook will take over from here and keep the preview in sync with the changes you make.
|
||||
// The `data` property will contain the live data of the document only when viewed from the Preview view of the Admin UI.
|
||||
const { data } = useLivePreview<PageData>({
|
||||
initialData: props.initialData,
|
||||
serverURL: "<PAYLOAD_SERVER_URL>",
|
||||
depth: 2,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ data.title }}</h1>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Building your own hook
|
||||
|
||||
|
||||
10
packages/live-preview-vue/.eslintignore
Normal file
10
packages/live-preview-vue/.eslintignore
Normal file
@@ -0,0 +1,10 @@
|
||||
.tmp
|
||||
**/.git
|
||||
**/.hg
|
||||
**/.pnp.*
|
||||
**/.svn
|
||||
**/.yarn/**
|
||||
**/build
|
||||
**/dist/**
|
||||
**/node_modules
|
||||
**/temp
|
||||
37
packages/live-preview-vue/.eslintrc.js
Normal file
37
packages/live-preview-vue/.eslintrc.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/** @type {import('prettier').Config} */
|
||||
module.exports = {
|
||||
extends: ['@payloadcms'],
|
||||
overrides: [
|
||||
{
|
||||
extends: ['plugin:@typescript-eslint/disable-type-checked'],
|
||||
files: ['*.js', '*.cjs', '*.json', '*.md', '*.yml', '*.yaml'],
|
||||
},
|
||||
{
|
||||
files: ['package.json', 'tsconfig.json'],
|
||||
rules: {
|
||||
'perfectionist/sort-array-includes': 'off',
|
||||
'perfectionist/sort-astro-attributes': 'off',
|
||||
'perfectionist/sort-classes': 'off',
|
||||
'perfectionist/sort-enums': 'off',
|
||||
'perfectionist/sort-exports': 'off',
|
||||
'perfectionist/sort-imports': 'off',
|
||||
'perfectionist/sort-interfaces': 'off',
|
||||
'perfectionist/sort-jsx-props': 'off',
|
||||
'perfectionist/sort-keys': 'off',
|
||||
'perfectionist/sort-maps': 'off',
|
||||
'perfectionist/sort-named-exports': 'off',
|
||||
'perfectionist/sort-named-imports': 'off',
|
||||
'perfectionist/sort-object-types': 'off',
|
||||
'perfectionist/sort-objects': 'off',
|
||||
'perfectionist/sort-svelte-attributes': 'off',
|
||||
'perfectionist/sort-union-types': 'off',
|
||||
'perfectionist/sort-vue-attributes': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
root: true,
|
||||
}
|
||||
10
packages/live-preview-vue/.prettierignore
Normal file
10
packages/live-preview-vue/.prettierignore
Normal file
@@ -0,0 +1,10 @@
|
||||
.tmp
|
||||
**/.git
|
||||
**/.hg
|
||||
**/.pnp.*
|
||||
**/.svn
|
||||
**/.yarn/**
|
||||
**/build
|
||||
**/dist/**
|
||||
**/node_modules
|
||||
**/temp
|
||||
15
packages/live-preview-vue/.swcrc
Normal file
15
packages/live-preview-vue/.swcrc
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/swcrc",
|
||||
"sourceMaps": "inline",
|
||||
"jsc": {
|
||||
"target": "esnext",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"tsx": true,
|
||||
"dts": true
|
||||
}
|
||||
},
|
||||
"module": {
|
||||
"type": "commonjs"
|
||||
}
|
||||
}
|
||||
56
packages/live-preview-vue/package.json
Normal file
56
packages/live-preview-vue/package.json
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-vue",
|
||||
"version": "0.1.0",
|
||||
"description": "The official live preview Vue SDK for Payload",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/payloadcms/payload.git",
|
||||
"directory": "packages/live-preview-vue"
|
||||
},
|
||||
"license": "MIT",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"author": "Payload CMS, Inc.",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:types",
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
|
||||
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
||||
"clean": "rimraf {dist,*.tsbuildinfo}",
|
||||
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
|
||||
"prepublishOnly": "pnpm clean && pnpm turbo build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/live-preview": "workspace:^0.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"vue": "^3.0.0",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.0"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./src/index.ts",
|
||||
"types": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"registry": "https://registry.npmjs.org/",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
58
packages/live-preview-vue/src/index.ts
Normal file
58
packages/live-preview-vue/src/index.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
import { ready, subscribe, unsubscribe } from '@payloadcms/live-preview'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
/**
|
||||
* Vue composable to implement Payload CMS Live Preview.
|
||||
*
|
||||
* {@link https://payloadcms.com/docs/live-preview/frontend View the documentation}
|
||||
*/
|
||||
export const useLivePreview = <T>(props: {
|
||||
apiRoute?: string
|
||||
depth?: number
|
||||
initialData: T
|
||||
serverURL: string
|
||||
}): {
|
||||
data: Ref<T>
|
||||
isLoading: Ref<boolean>
|
||||
} => {
|
||||
const { apiRoute, depth, initialData, serverURL } = props
|
||||
const data = ref(initialData) as Ref<T>
|
||||
const isLoading = ref(true)
|
||||
const hasSentReadyMessage = ref(false)
|
||||
|
||||
const onChange = (mergedData: T) => {
|
||||
data.value = mergedData
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
let subscription: (event: MessageEvent) => void
|
||||
|
||||
onMounted(() => {
|
||||
subscription = subscribe({
|
||||
apiRoute,
|
||||
callback: onChange,
|
||||
depth,
|
||||
initialData,
|
||||
serverURL,
|
||||
})
|
||||
|
||||
if (!hasSentReadyMessage.value) {
|
||||
hasSentReadyMessage.value = true
|
||||
|
||||
ready({
|
||||
serverURL,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
unsubscribe(subscription)
|
||||
})
|
||||
|
||||
return {
|
||||
data,
|
||||
isLoading,
|
||||
}
|
||||
}
|
||||
25
packages/live-preview-vue/tsconfig.json
Normal file
25
packages/live-preview-vue/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true, // Make sure typescript knows that this module depends on their references
|
||||
"noEmit": false /* Do not emit outputs. */,
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||
"rootDir": "./src" /* Specify the root folder within your source files. */,
|
||||
"jsx": "react"
|
||||
},
|
||||
"exclude": [
|
||||
"dist",
|
||||
"build",
|
||||
"tests",
|
||||
"test",
|
||||
"node_modules",
|
||||
".eslintrc.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.spec.tsx"
|
||||
],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts", "src/**/*.json"],
|
||||
"references": [{ "path": "../payload" }] // db-mongodb depends on payload
|
||||
}
|
||||
115
pnpm-lock.yaml
generated
115
pnpm-lock.yaml
generated
@@ -595,6 +595,22 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:../payload
|
||||
|
||||
packages/live-preview-vue:
|
||||
dependencies:
|
||||
'@payloadcms/live-preview':
|
||||
specifier: workspace:^0.x
|
||||
version: link:../live-preview
|
||||
devDependencies:
|
||||
'@payloadcms/eslint-config':
|
||||
specifier: workspace:*
|
||||
version: link:../eslint-config-payload
|
||||
payload:
|
||||
specifier: workspace:*
|
||||
version: link:../payload
|
||||
vue:
|
||||
specifier: ^3.0.0
|
||||
version: 3.4.23(typescript@5.4.4)
|
||||
|
||||
packages/next:
|
||||
dependencies:
|
||||
'@dnd-kit/core':
|
||||
@@ -6446,6 +6462,79 @@ packages:
|
||||
undici: 5.28.4
|
||||
dev: true
|
||||
|
||||
/@vue/compiler-core@3.4.23:
|
||||
resolution: {integrity: sha512-HAFmuVEwNqNdmk+w4VCQ2pkLk1Vw4XYiiyxEp3z/xvl14aLTUBw2OfVH3vBcx+FtGsynQLkkhK410Nah1N2yyQ==}
|
||||
dependencies:
|
||||
'@babel/parser': 7.24.4
|
||||
'@vue/shared': 3.4.23
|
||||
entities: 4.5.0
|
||||
estree-walker: 2.0.2
|
||||
source-map-js: 1.2.0
|
||||
dev: true
|
||||
|
||||
/@vue/compiler-dom@3.4.23:
|
||||
resolution: {integrity: sha512-t0b9WSTnCRrzsBGrDd1LNR5HGzYTr7LX3z6nNBG+KGvZLqrT0mY6NsMzOqlVMBKKXKVuusbbB5aOOFgTY+senw==}
|
||||
dependencies:
|
||||
'@vue/compiler-core': 3.4.23
|
||||
'@vue/shared': 3.4.23
|
||||
dev: true
|
||||
|
||||
/@vue/compiler-sfc@3.4.23:
|
||||
resolution: {integrity: sha512-fSDTKTfzaRX1kNAUiaj8JB4AokikzStWgHooMhaxyjZerw624L+IAP/fvI4ZwMpwIh8f08PVzEnu4rg8/Npssw==}
|
||||
dependencies:
|
||||
'@babel/parser': 7.24.4
|
||||
'@vue/compiler-core': 3.4.23
|
||||
'@vue/compiler-dom': 3.4.23
|
||||
'@vue/compiler-ssr': 3.4.23
|
||||
'@vue/shared': 3.4.23
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.30.10
|
||||
postcss: 8.4.38
|
||||
source-map-js: 1.2.0
|
||||
dev: true
|
||||
|
||||
/@vue/compiler-ssr@3.4.23:
|
||||
resolution: {integrity: sha512-hb6Uj2cYs+tfqz71Wj6h3E5t6OKvb4MVcM2Nl5i/z1nv1gjEhw+zYaNOV+Xwn+SSN/VZM0DgANw5TuJfxfezPg==}
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.4.23
|
||||
'@vue/shared': 3.4.23
|
||||
dev: true
|
||||
|
||||
/@vue/reactivity@3.4.23:
|
||||
resolution: {integrity: sha512-GlXR9PL+23fQ3IqnbSQ8OQKLodjqCyoCrmdLKZk3BP7jN6prWheAfU7a3mrltewTkoBm+N7qMEb372VHIkQRMQ==}
|
||||
dependencies:
|
||||
'@vue/shared': 3.4.23
|
||||
dev: true
|
||||
|
||||
/@vue/runtime-core@3.4.23:
|
||||
resolution: {integrity: sha512-FeQ9MZEXoFzFkFiw9MQQ/FWs3srvrP+SjDKSeRIiQHIhtkzoj0X4rWQlRNHbGuSwLra6pMyjAttwixNMjc/xLw==}
|
||||
dependencies:
|
||||
'@vue/reactivity': 3.4.23
|
||||
'@vue/shared': 3.4.23
|
||||
dev: true
|
||||
|
||||
/@vue/runtime-dom@3.4.23:
|
||||
resolution: {integrity: sha512-RXJFwwykZWBkMiTPSLEWU3kgVLNAfActBfWFlZd0y79FTUxexogd0PLG4HH2LfOktjRxV47Nulygh0JFXe5f9A==}
|
||||
dependencies:
|
||||
'@vue/runtime-core': 3.4.23
|
||||
'@vue/shared': 3.4.23
|
||||
csstype: 3.1.3
|
||||
dev: true
|
||||
|
||||
/@vue/server-renderer@3.4.23(vue@3.4.23):
|
||||
resolution: {integrity: sha512-LDwGHtnIzvKFNS8dPJ1SSU5Gvm36p2ck8wCZc52fc3k/IfjKcwCyrWEf0Yag/2wTFUBXrqizfhK9c/mC367dXQ==}
|
||||
peerDependencies:
|
||||
vue: 3.4.23
|
||||
dependencies:
|
||||
'@vue/compiler-ssr': 3.4.23
|
||||
'@vue/shared': 3.4.23
|
||||
vue: 3.4.23(typescript@5.4.4)
|
||||
dev: true
|
||||
|
||||
/@vue/shared@3.4.23:
|
||||
resolution: {integrity: sha512-wBQ0gvf+SMwsCQOyusNw/GoXPV47WGd1xB5A1Pgzy0sQ3Bi5r5xm3n+92y3gCnB3MWqnRDdvfkRGxhKtbBRNgg==}
|
||||
dev: true
|
||||
|
||||
/@webassemblyjs/ast@1.12.1:
|
||||
resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==}
|
||||
dependencies:
|
||||
@@ -9349,6 +9438,10 @@ packages:
|
||||
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
|
||||
engines: {node: '>=4.0'}
|
||||
|
||||
/estree-walker@2.0.2:
|
||||
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||
dev: true
|
||||
|
||||
/esutils@2.0.3:
|
||||
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -11870,6 +11963,12 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/magic-string@0.30.10:
|
||||
resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==}
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
dev: true
|
||||
|
||||
/make-dir@3.1.0:
|
||||
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -15969,6 +16068,22 @@ packages:
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/vue@3.4.23(typescript@5.4.4):
|
||||
resolution: {integrity: sha512-X1y6yyGJ28LMUBJ0k/qIeKHstGd+BlWQEOT40x3auJFTmpIhpbKLgN7EFsqalnJXq1Km5ybDEsp6BhuWKciUDg==}
|
||||
peerDependencies:
|
||||
typescript: 5.4.4
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.4.23
|
||||
'@vue/compiler-sfc': 3.4.23
|
||||
'@vue/runtime-dom': 3.4.23
|
||||
'@vue/server-renderer': 3.4.23(vue@3.4.23)
|
||||
'@vue/shared': 3.4.23
|
||||
typescript: 5.4.4
|
||||
dev: true
|
||||
|
||||
/w3c-xmlserializer@4.0.0:
|
||||
resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
Reference in New Issue
Block a user