Skip to content

Commit

Permalink
Improve bundle size with reader API in React server component environ…
Browse files Browse the repository at this point in the history
…ments (#618)
  • Loading branch information
emmatown authored Sep 5, 2023
1 parent bbbd7d6 commit 43f0b61
Show file tree
Hide file tree
Showing 53 changed files with 177 additions and 104 deletions.
5 changes: 5 additions & 0 deletions .changeset/witty-olives-obey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@keystatic/core": patch
---

Fixed bundle size increase when using `@keystatic/core/reader` with React server components
12 changes: 12 additions & 0 deletions packages/keystatic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,18 @@
"#sha1": {
"node": "./src/sha1/node.ts",
"default": "./src/sha1/webcrypto.ts"
},
"#field-ui/*": {
"react-server": "./src/form/fields/empty-field-ui.tsx",
"default": "./src/form/fields/*/ui.tsx"
},
"#component-block-primitives": {
"react-server": "./src/form/fields/document/DocumentEditor/primitives/blank-for-react-server.tsx",
"default": "./src/form/fields/document/DocumentEditor/primitives/index.tsx"
},
"#cloud-image-preview": {
"react-server": "./src/component-blocks/blank-for-react-server.tsx",
"default": "./src/component-blocks/cloud-image-preview.tsx"
}
}
}
2 changes: 1 addition & 1 deletion packages/keystatic/src/app/useItemData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
KEYSTATIC_CLOUD_HEADERS,
MaybePromise,
} from './utils';
import { toFormattedFormDataError } from '../form/errors';
import { toFormattedFormDataError } from '../form/error-formatting';
import { serializeRepoConfig } from './repo-config';

function parseEntry(args: UseItemDataArgs, files: Map<string, Uint8Array>) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export function CloudImagePreview() {}
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
'use client';

import { useEffect, useState } from 'react';
import { useSelected, useSlateStatic } from 'slate-react';
import { useOverlayTriggerState } from '@react-stately/overlays';
Expand Down
2 changes: 1 addition & 1 deletion packages/keystatic/src/component-blocks/cloud-image.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { component } from '..';
import { CloudImagePreview } from './cloud-image-preview';
import { CloudImagePreview } from '#cloud-image-preview';
import { cloudImageSchema } from './cloud-image-schema';

/** @deprecated Experimental */
Expand Down
32 changes: 32 additions & 0 deletions packages/keystatic/src/form/error-formatting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { FieldDataError } from './fields/error';
import { PropValidationError } from './parse-props';

function flattenErrors(error: unknown): unknown[] {
if (error instanceof AggregateError) {
return error.errors.flatMap(flattenErrors);
}
return [error];
}

export function formatFormDataError(error: unknown) {
const flatErrors = flattenErrors(error);

return flatErrors
.map(error => {
if (error instanceof PropValidationError) {
const path = error.path.join('.');
return `${path}: ${
error.cause instanceof FieldDataError
? error.cause.message
: `Unexpected error: ${error.cause}`
}`;
}
return `Unexpected error: ${error}`;
})
.join('\n');
}

export function toFormattedFormDataError(error: unknown) {
const formatted = formatFormDataError(error);
return new Error(`Field validation failed:\n` + formatted);
}
34 changes: 2 additions & 32 deletions packages/keystatic/src/form/errors.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,10 @@
import { getSlugFromState } from '../app/utils';
import { ComponentSchema } from './api';
import { SlugFieldInfo } from './fields/text/path-slug-context';
import { FieldDataError } from './fields/error';
import { PropValidationError } from './parse-props';
import { ReadonlyPropPath } from './fields/document/DocumentEditor/component-blocks/utils';
import { validateArrayLength } from './validate-array-length';

function flattenErrors(error: unknown): unknown[] {
if (error instanceof AggregateError) {
return error.errors.flatMap(flattenErrors);
}
return [error];
}

export function formatFormDataError(error: unknown) {
const flatErrors = flattenErrors(error);

return flatErrors
.map(error => {
if (error instanceof PropValidationError) {
const path = error.path.join('.');
return `${path}: ${
error.cause instanceof FieldDataError
? error.cause.message
: `Unexpected error: ${error.cause}`
}`;
}
return `Unexpected error: ${error}`;
})
.join('\n');
}

export function toFormattedFormDataError(error: unknown) {
const formatted = formatFormDataError(error);
return new Error(`Field validation failed:\n` + formatted);
}
import { toFormattedFormDataError } from './error-formatting';

export function clientSideValidateProp(
schema: ComponentSchema,
Expand All @@ -50,7 +20,7 @@ export function clientSideValidateProp(
}
}

export function validateValueWithSchema(
function validateValueWithSchema(
schema: ComponentSchema,
value: any,
slugField: SlugFieldInfo | undefined,
Expand Down
2 changes: 1 addition & 1 deletion packages/keystatic/src/form/fields/blocks/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
BasicFormField,
fields,
} from '../../api';
import { BlocksFieldInput } from './ui';
import { BlocksFieldInput } from '#field-ui/blocks';

export function blocks<Schemas extends Record<string, ComponentSchema>>(
blocks: {
Expand Down
1 change: 0 additions & 1 deletion packages/keystatic/src/form/fields/blocks/ui.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
'use client';
import { useLocalizedStringFormatter } from '@react-aria/i18n';
import { ActionButton, ButtonGroup, Button } from '@keystar/ui/button';
import {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Checkbox } from '@keystar/ui/checkbox';
import { Text } from '@keystar/ui/typography';
import { BasicFormField } from '../api';
import { FieldDataError } from './error';
import { basicFormFieldWithSimpleReaderParse } from './utils';
import { CheckboxFieldInput } from '#field-ui/checkbox';
import { BasicFormField } from '../../api';
import { FieldDataError } from '../error';
import { basicFormFieldWithSimpleReaderParse } from '../utils';

export function checkbox({
label,
Expand All @@ -14,12 +13,13 @@ export function checkbox({
description?: string;
}): BasicFormField<boolean> {
return basicFormFieldWithSimpleReaderParse({
Input({ value, onChange, autoFocus }) {
Input(props) {
return (
<Checkbox isSelected={value} onChange={onChange} autoFocus={autoFocus}>
<Text>{label}</Text>
{description && <Text slot="description">{description}</Text>}
</Checkbox>
<CheckboxFieldInput
{...props}
label={label}
description={description}
/>
);
},
defaultValue() {
Expand Down
21 changes: 21 additions & 0 deletions packages/keystatic/src/form/fields/checkbox/ui.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { FormFieldInputProps } from '../../api';
import { Checkbox } from '@keystar/ui/checkbox';
import { Text } from '@keystar/ui/typography';

export function CheckboxFieldInput(
props: FormFieldInputProps<boolean> & {
label: string;
description?: string;
}
) {
return (
<Checkbox
isSelected={props.value}
onChange={props.onChange}
autoFocus={props.autoFocus}
>
<Text>{props.label}</Text>
{props.description && <Text slot="description">{props.description}</Text>}
</Checkbox>
);
}
2 changes: 1 addition & 1 deletion packages/keystatic/src/form/fields/cloudImage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ObjectField } from '../../api';
import { integer } from '../integer';
import { object } from '../object';
import { text } from '../text';
import { CloudImageFieldInput } from './ui';
import { CloudImageFieldInput } from '#field-ui/cloudImage';

export function cloudImage({
label,
Expand Down
2 changes: 0 additions & 2 deletions packages/keystatic/src/form/fields/cloudImage/ui.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
'use client';

import { useEffect, useId, useState } from 'react';

import { ActionButton, ClearButton, ToggleButton } from '@keystar/ui/button';
Expand Down
2 changes: 1 addition & 1 deletion packages/keystatic/src/form/fields/date/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
assertRequired,
basicFormFieldWithSimpleReaderParse,
} from '../utils';
import { DateFieldInput } from './ui';
import { DateFieldInput } from '#field-ui/date';
import { validateDate } from './validateDate';

export function date<IsRequired extends boolean | undefined>({
Expand Down
1 change: 0 additions & 1 deletion packages/keystatic/src/form/fields/date/ui.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
'use client';
import { TextField } from '@keystar/ui/text-field';
import { useReducer } from 'react';
import { validateDate } from './validateDate';
Expand Down
2 changes: 1 addition & 1 deletion packages/keystatic/src/form/fields/datetime/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
assertRequired,
basicFormFieldWithSimpleReaderParse,
} from '../utils';
import { DatetimeFieldInput } from './ui';
import { DatetimeFieldInput } from '#field-ui/datetime';
import { validateDatetime } from './validateDatetime';

export function datetime<IsRequired extends boolean | undefined>({
Expand Down
2 changes: 1 addition & 1 deletion packages/keystatic/src/form/fields/datetime/ui.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//ui.tsx
'use client';

import { TextField } from '@keystar/ui/text-field';
import { useReducer } from 'react';
import { validateDatetime } from './validateDatetime';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function BlockWrapper() {}
export function NotEditable() {}
export function ToolbarSeparator() {}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
'use client';
export {
ActiveBlockPopoverProvider,
BlockPopover,
Expand Down
2 changes: 1 addition & 1 deletion packages/keystatic/src/form/fields/document/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
FormFieldStoredValue,
} from '../../api';
import { text } from '../text';
import { DocumentFieldInput } from './ui';
import { DocumentFieldInput } from '#field-ui/document';
import { createDocumentEditorForNormalization } from './DocumentEditor/create-editor';
import { object } from '../object';
import { FieldDataError } from '../error';
Expand Down
2 changes: 0 additions & 2 deletions packages/keystatic/src/form/fields/document/ui.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
'use client';

import { Field, FieldProps } from '@keystar/ui/field';
import { useState } from 'react';

Expand Down
30 changes: 30 additions & 0 deletions packages/keystatic/src/form/fields/empty-field-ui.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// this is used in react-server environments to avoid bundling UI when the reader API is used
// if you added a new field and get an error that there's missing a missing export here,
// you probably just need to add another empty export here

function empty() {
throw new Error(
"unexpected call to function that shouldn't be called in React server component environment"
);
}

export let SlugFieldInput = empty,
TextFieldInput = empty,
UrlFieldInput = empty,
SelectFieldInput = empty,
RelationshipInput = empty,
PathReferenceInput = empty,
MultiselectFieldInput = empty,
IntegerFieldInput = empty,
ImageFieldInput = empty,
FileFieldInput = empty,
DatetimeFieldInput = empty,
DateFieldInput = empty,
CloudImageFieldInput = empty,
BlocksFieldInput = empty,
DocumentFieldInput = empty,
CheckboxFieldInput = empty,
createEditorSchema = empty,
getDefaultValue = empty,
parseToEditorState = empty,
serializeFromEditorState = empty;
2 changes: 1 addition & 1 deletion packages/keystatic/src/form/fields/file/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AssetFormField } from '../../api';
import { FieldDataError } from '../error';
import { getSrcPrefix } from '../image/getSrcPrefix';
import { RequiredValidation, assertRequired } from '../utils';
import { FileFieldInput } from './ui';
import { FileFieldInput } from '#field-ui/file';

export function file<IsRequired extends boolean | undefined>({
label,
Expand Down
1 change: 0 additions & 1 deletion packages/keystatic/src/form/fields/file/ui.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
'use client';
import { ButtonGroup, ActionButton, Button } from '@keystar/ui/button';
import { FieldLabel, FieldMessage } from '@keystar/ui/field';
import { Flex } from '@keystar/ui/layout';
Expand Down
2 changes: 1 addition & 1 deletion packages/keystatic/src/form/fields/image/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AssetFormField } from '../../api';
import { FieldDataError } from '../error';
import { RequiredValidation, assertRequired } from '../utils';
import { getSrcPrefix } from './getSrcPrefix';
import { ImageFieldInput } from './ui';
import { ImageFieldInput } from '#field-ui/image';

export function image<IsRequired extends boolean | undefined>({
label,
Expand Down
1 change: 0 additions & 1 deletion packages/keystatic/src/form/fields/image/ui.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
'use client';
import { ButtonGroup, ActionButton } from '@keystar/ui/button';
import { FieldLabel, FieldMessage } from '@keystar/ui/field';
import { Flex, Box } from '@keystar/ui/layout';
Expand Down
2 changes: 1 addition & 1 deletion packages/keystatic/src/form/fields/integer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
assertRequired,
basicFormFieldWithSimpleReaderParse,
} from '../utils';
import { IntegerFieldInput } from './ui';
import { IntegerFieldInput } from '#field-ui/integer';
import { validateInteger } from './validateInteger';

export function integer<IsRequired extends boolean | undefined>({
Expand Down
1 change: 0 additions & 1 deletion packages/keystatic/src/form/fields/integer/ui.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
'use client';
import { NumberField } from '@keystar/ui/number-field';
import { useReducer } from 'react';
import { validateInteger } from './validateInteger';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
'use client';
import { EditorState, Selection } from 'prosemirror-state';
import { history } from 'prosemirror-history';
import { keymap } from 'prosemirror-keymap';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
'use client';
import { Ast, Node as MarkdocNode, ValidateError } from '@markdoc/markdoc';
import {
Mark,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
'use client';
import { Ast, Node as MarkdocNode, NodeType } from '@markdoc/markdoc';
import { Fragment, Mark, Node as ProseMirrorNode } from 'prosemirror-model';
import { getEditorSchema } from '../schema';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
'use client';
export { BlockPopover } from './BlockPopover';

export * from './toolbar';
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
'use client';
import { css, tokenSchema } from '@keystar/ui/style';
import {
DOMOutputSpec,
Expand Down
Loading

3 comments on commit 43f0b61

@vercel
Copy link

@vercel vercel bot commented on 43f0b61 Sep 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

keystatic – ./dev-projects/next-app

keystatic-thinkmill-labs.vercel.app
keystatic-git-main-thinkmill-labs.vercel.app
keystatic.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 43f0b61 Sep 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 43f0b61 Sep 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

keystar-ui – ./design-system/docs

keystar-ui.vercel.app
keystar-ui-git-main-thinkmill-labs.vercel.app
keystar-ui-thinkmill-labs.vercel.app
voussoir.vercel.app

Please sign in to comment.