Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion apps/docs/app/(diffs)/_docs/DocsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ import {
VIRTUALIZATION_REACT_CONFIG,
VIRTUALIZATION_VANILLA_DIFF,
} from '../docs/Virtualization/constants';
import {
VUE_API_MULTI_FILE_DIFF,
VUE_API_PATCH_DIFF,
VUE_API_SLOTS,
VUE_API_SSR,
VUE_API_VIRTUALIZER,
VUE_API_WORKER_POOL,
} from '../docs/VueAPI/constants';
import {
WORKER_POOL_API_REFERENCE,
WORKER_POOL_ARCHITECTURE_ASCII,
Expand Down Expand Up @@ -117,7 +125,7 @@ import { renderMDX } from '@/lib/mdx';

const docsTitle = 'Diffs docs';
const docsDescription =
'Documentation for @pierre/diffs: React and vanilla APIs, virtualization, theming, token hooks, the worker pool, and SSR hydration.';
'Documentation for @pierre/diffs: React, Vue, and vanilla APIs, virtualization, theming, token hooks, the worker pool, and SSR hydration.';

// Next.js replaces (does not deep-merge) nested metadata objects like
// `openGraph` and `twitter` from parent segments. Re-declare `images` here
Expand Down Expand Up @@ -149,6 +157,7 @@ export default function DocsPage() {
<InstallationSection />
<CoreTypesSection />
<ReactAPISection />
<VueAPISection />
<VanillaAPISection />
<VirtualizationSection />
<CustomHunkSeparatorsSection />
Expand Down Expand Up @@ -324,6 +333,36 @@ async function VanillaAPISection() {
return <ProseWrapper>{content}</ProseWrapper>;
}

async function VueAPISection() {
const [
vueAPIMultiFileDiff,
vueAPIPatchDiff,
vueAPISlots,
vueAPIVirtualizer,
vueAPIWorkerPool,
vueAPISsr,
] = await Promise.all([
preloadFile(VUE_API_MULTI_FILE_DIFF),
preloadFile(VUE_API_PATCH_DIFF),
preloadFile(VUE_API_SLOTS),
preloadFile(VUE_API_VIRTUALIZER),
preloadFile(VUE_API_WORKER_POOL),
preloadFile(VUE_API_SSR),
]);
const content = await renderMDX({
filePath: '(diffs)/docs/VueAPI/content.mdx',
scope: {
vueAPIMultiFileDiff,
vueAPIPatchDiff,
vueAPISlots,
vueAPISsr,
vueAPIVirtualizer,
vueAPIWorkerPool,
},
});
return <ProseWrapper>{content}</ProseWrapper>;
}

async function VirtualizationSection() {
const [
reactVirtualizerBasic,
Expand Down
1 change: 1 addition & 0 deletions apps/docs/app/(diffs)/docs/Installation/content.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ The package provides several entry points for different use cases:
| ---------------------- | ------------------------------------------------------------------------------------------------------------ |
| `@pierre/diffs` | [Vanilla JS components](#vanilla-js-api) and [utility functions](#utilities) for parsing and rendering diffs |
| `@pierre/diffs/react` | [React components](#react-api) for rendering diffs with full interactivity |
| `@pierre/diffs/vue` | [Vue components](#vue-api) for rendering diffs with full interactivity |
| `@pierre/diffs/ssr` | [Server-side rendering utilities](#ssr) for pre-rendering diffs with syntax highlighting |
| `@pierre/diffs/worker` | [Worker pool utilities](#worker-pool) for offloading syntax highlighting to background threads |
13 changes: 7 additions & 6 deletions apps/docs/app/(diffs)/docs/Overview/content.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ We have an opinionated stance in our architecture: **browsers are rather
efficient at rendering raw HTML**. We lean into this by having all the lower
level APIs purely rendering strings (the raw HTML) that are then consumed by
higher-order components and utilities. This gives us great performance and
flexibility to support popular libraries like React as well as provide great
tools if you want to stick to vanilla JavaScript and HTML. The higher-order
components render all this out into Shadow DOM and CSS grid layout.
flexibility to support popular libraries like React and Vue as well as provide
great tools if you want to stick to vanilla JavaScript and HTML. The
higher-order components render all this out into Shadow DOM and CSS grid layout.

Generally speaking, you're probably going to want to use the higher level
components since they provide an easy-to-use API that you can get started with
rather quickly. We currently only have components for vanilla JavaScript and
React, but will add more if there's demand.
rather quickly. Diffs includes runtime lanes for React, Vue, and vanilla
JavaScript.

For this overview, we'll talk about the vanilla JavaScript components for now
but there are React equivalents for all of these.
but there are React and Vue equivalents for all of these.

## Rendering Diffs

Expand All @@ -43,6 +43,7 @@ There are two ways to render diffs with `FileDiff`:
2. Consume a patch file

You can see examples of these approaches below, in both JavaScript and React.
For Vue usage, see the [Vue API](#vue-api).

<CodeToggle
reactSingleFile={reactSingleFile}
Expand Down
9 changes: 4 additions & 5 deletions apps/docs/app/(diffs)/docs/SSR/content.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,13 @@ content of the diffs itself.
### Usage

Each preload function returns an object containing the original inputs plus a
`prerenderedHTML` string. This object can be spread directly into the
corresponding React component for automatic hydration.
`prerenderedHTML` string. This object can be passed to the corresponding React
or Vue component for automatic hydration, or used with the vanilla APIs.

<Notice variant="warning" icon={<IconCiWarningFill />}>
Inputs used for pre-rendering must exactly match what's rendered in the client
component. We recommend spreading the entire result object into your File or
Diff component to ensure the client receives the same inputs that were used to
generate the pre-rendered HTML.
component. Keep the client props aligned with the preload result so the client
receives the same inputs that were used to generate the pre-rendered HTML.
</Notice>

#### Server Component
Expand Down
23 changes: 12 additions & 11 deletions apps/docs/app/(diffs)/docs/Virtualization/content.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,24 @@ window). Directly inside that container, add a content wrapper that holds all
diff/file instances and any other content you render. The virtualizer uses this
wrapper to track content size changes.

Inside that scroll container, render the `VirtualizedFile` and
`VirtualizedFileDiff` components. In React, this is handled automatically by the
built-in `Virtualizer` context. In vanilla JS, you manage this explicitly by
creating a `Virtualizer` instance and wiring it to `VirtualizedFile` /
`VirtualizedFileDiff` instead of the traditional APIs.
Inside that scroll container, render file and diff components. In React and Vue,
this is handled automatically by the built-in `Virtualizer` provider. In vanilla
JS, you manage this explicitly by creating a `Virtualizer` instance and wiring
it to `VirtualizedFile` / `VirtualizedFileDiff` instead of the traditional APIs.

<Notice icon={<IconInfoFill />}>
These APIs are still early and are subject to change. We may merge related
virtualization functionality into the top-level `File` and `FileDiff`
components before shipping, so please share feedback as you test these APIs.
</Notice>

### React
### React and Vue

In React, wrap your diff/file components in `Virtualizer` from
`@pierre/diffs/react`. The `Virtualizer` component is your scroll container.
Currently, the React wrapper does not support window scrolling unless you
orchestrate your own provider via `VirtualizerContext.Provider` (from
`@pierre/diffs/react`) and pass a manually created `Virtualizer` instance (from
`@pierre/diffs`).
`@pierre/diffs/react`. In Vue, use `Virtualizer` from `@pierre/diffs/vue`. The
`Virtualizer` component is your scroll container. Currently, framework wrappers
do not support window scrolling unless you orchestrate your own provider and
pass a manually created `Virtualizer` instance from `@pierre/diffs`.

<DocsCodeExample {...reactVirtualizerBasic} />

Expand All @@ -61,6 +59,9 @@ You can tune virtualization behavior with the `config` prop.
- `className` / `style`: applied to the outer scroll root
- `contentClassName` / `contentStyle`: applied to the inner content wrapper

In Vue templates, use `class` / `style` on the outer component and
`content-class` / `content-style` for the inner content wrapper.

### Vanilla JavaScript

In vanilla JS, create a `Virtualizer` instance and pass it into
Expand Down
149 changes: 149 additions & 0 deletions apps/docs/app/(diffs)/docs/VueAPI/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import type { PreloadFileOptions } from '@pierre/diffs/ssr';

export const VUE_API_MULTI_FILE_DIFF: PreloadFileOptions<undefined> = {
file: {
name: 'multi-file-diff.vue',
contents: `<script setup lang="ts">
import { MultiFileDiff } from '@pierre/diffs/vue';
import type { FileContents } from '@pierre/diffs';

const props = defineProps<{
oldFile: FileContents;
newFile: FileContents;
}>();
</script>

<template>
<MultiFileDiff
:old-file="props.oldFile"
:new-file="props.newFile"
:options="{ theme: 'pierre-dark' }"
/>
</template>`,
},
};

export const VUE_API_PATCH_DIFF: PreloadFileOptions<undefined> = {
file: {
name: 'patch-diff.vue',
contents: `<script setup lang="ts">
import { PatchDiff } from '@pierre/diffs/vue';

const props = defineProps<{
patch: string;
}>();
</script>

<template>
<PatchDiff
:patch="props.patch"
:options="{ diffStyle: 'unified' }"
/>
</template>`,
},
};

export const VUE_API_SLOTS: PreloadFileOptions<undefined> = {
file: {
name: 'annotated-diff.vue',
contents: `<script setup lang="ts">
import { FileDiff } from '@pierre/diffs/vue';
import type { DiffLineAnnotation, FileDiffMetadata } from '@pierre/diffs';

const props = defineProps<{
fileDiff: FileDiffMetadata;
annotations: DiffLineAnnotation<{ body: string }>[];
}>();
</script>

<template>
<FileDiff
:file-diff="props.fileDiff"
:line-annotations="props.annotations"
>
<template #header="{ fileDiff }">
<strong>{{ fileDiff.name }}</strong>
</template>
<template #annotation="{ annotation }">
<p>{{ annotation.metadata.body }}</p>
</template>
</FileDiff>
</template>`,
},
};

export const VUE_API_VIRTUALIZER: PreloadFileOptions<undefined> = {
file: {
name: 'virtualized-diffs.vue',
contents: `<script setup lang="ts">
import { FileDiff, Virtualizer } from '@pierre/diffs/vue';
import type { FileDiffMetadata } from '@pierre/diffs';

const props = defineProps<{
files: FileDiffMetadata[];
}>();
</script>

<template>
<Virtualizer class="diff-scroll" :content-style="{ display: 'grid', gap: '16px' }">
<FileDiff
v-for="fileDiff in props.files"
:key="fileDiff.name"
:file-diff="fileDiff"
/>
</Virtualizer>
</template>`,
},
};

export const VUE_API_WORKER_POOL: PreloadFileOptions<undefined> = {
file: {
name: 'worker-pool.vue',
contents: `<script setup lang="ts">
import {
FileDiff,
WorkerPoolProvider,
} from '@pierre/diffs/vue';
import workerUrl from '@pierre/diffs/worker/worker.js?worker&url';
import type { FileDiffMetadata } from '@pierre/diffs';

const props = defineProps<{
fileDiff: FileDiffMetadata;
}>();
</script>

<template>
<WorkerPoolProvider
:pool-options="{
workerFactory: () => new Worker(workerUrl, { type: 'module' }),
}"
:highlighter-options="{ langs: ['typescript'], theme: 'pierre-dark' }"
>
<FileDiff :file-diff="props.fileDiff" />
</WorkerPoolProvider>
</template>`,
},
};

export const VUE_API_SSR: PreloadFileOptions<undefined> = {
file: {
name: 'hydrated-file.vue',
contents: `<script setup lang="ts">
import { File } from '@pierre/diffs/vue';
import type { PreloadedFileResult } from '@pierre/diffs/ssr';

const props = defineProps<{
preloadedFile: PreloadedFileResult<undefined>;
}>();
</script>

<template>
<File
:file="props.preloadedFile.file"
:options="props.preloadedFile.options"
:line-annotations="props.preloadedFile.annotations"
:prerendered-html="props.preloadedFile.prerenderedHTML"
/>
</template>`,
},
};
55 changes: 55 additions & 0 deletions apps/docs/app/(diffs)/docs/VueAPI/content.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
## Vue API

<Notice icon={<IconBulbFill />}>
Import Vue components from `@pierre/diffs/vue`.
</Notice>

The Vue API mirrors the React component lane while using Vue props and slots.
Use `File` for a single file, `FileDiff` for parsed diff metadata,
`MultiFileDiff` for two file versions, `PatchDiff` for one patch string, and
`UnresolvedFile` for merge-conflict files.

### Rendering Diffs

Use `MultiFileDiff` when you have both file versions and want Diffs to parse the
change for you.

<DocsCodeExample {...vueAPIMultiFileDiff} />

Use `PatchDiff` when you have a single-file unified patch string.

<DocsCodeExample {...vueAPIPatchDiff} />

### Slots

Slots replace React render props. Header slots receive the file or diff object,
annotation slots receive the annotation, and gutter utility slots receive a
`getHoveredLine` function.

<DocsCodeExample {...vueAPISlots} />

`UnresolvedFile` also exposes
`#merge-conflict-utility="{ action, context, getInstance }"`. Call
`context.resolveConflict('current' | 'incoming' | 'both')` from your slot UI to
resolve a conflict.

### Virtualization

Wrap large lists in `Virtualizer`. Nested `File` and `FileDiff` components use
virtualized renderer instances automatically.

<DocsCodeExample {...vueAPIVirtualizer} />

### Worker Pool

Use `WorkerPoolProvider` when syntax highlighting should run off the main
thread. Nested Vue Diffs components inherit the shared worker pool.

<DocsCodeExample {...vueAPIWorkerPool} />

### SSR

Server preload helpers return `prerenderedHTML`. Pass it to the matching Vue
component so the client can hydrate the existing Shadow DOM.

<DocsCodeExample {...vueAPISsr} />
Loading