# Sitecore BYOC components

A repository of react components authored by Sitecore that offers integration of different products in an
easy-to-consume package. The promise of it is that it's mostly plug-and-play business.

# What is BYOC?

BYOC is a way to register react components into Pages/Components app from within User app. It’s a streamlined system
that makes it easy to add functionality in a way that is familiar to the regular developer.

Using BYOC requires two steps:

1.  Defining a react component (regular everyday react component, not sxa)
2.  Registering that component (using BYOC.registerComponent call).

## Use cases

BYOC components serve two audiences, and solve two problems:

### User own components

In this the component is defined and registered from within the User app
([example](https://github.com/Sitecore/feaas-nextjs-example/blob/main/app/byoc/ExampleClientsideComponent.tsx#L48 'https://github.com/Sitecore/feaas-nextjs-example/blob/main/app/byoc/ExampleClientsideComponent.tsx#L48')).
Sitecore does not really have access to the source and definition of those components, and yet they appear in the UI as
if they were native. It’s really good way for the users to extend their Sitecore websites, as they can use any tools,
dependencies and techniques. They can choose to re-use their components, or create one-offs specific to the app. It goes
as far as supporting hot-reloading their code right in the browser within the context of developer’s next.js render
host.

### Sitecore components

The other large use case for BYOC is to allow Sitecore products to be integrated together easily, using a shared
mechanism allows making changes easier. It shortens the release cycle, and de-risks the new additions to the ecosystem.
Unlike user components, the sitecore components are imported from an npm package. New components become an opt-in to the
user app, the user only needs to update the version of the npm package, and import what they need in their app.

## Modes of operation

BYOC components attempt to offer solutions for most of the developer needs, beyond what’s typically expected.
Essentially it’s all different combinations of rendering component on server or client-side.

### Rendered on client

The basic use case is a component that adds interactivity on the page, such as smart input field, or a tool to display
some dynamic content (e.g. sitecore forms). In this case, the component has to be loaded on the clientside. In next.js
it may not be so straighforward, as it tends to optimize out the components that aren’t used. Additionally, in app
router setup of next.js 13, all components are by default considered server side, so clientside components need to be
opt-in. The solution to this is to define a specia bundle component that lists all the components that are expected to
work on clientside, and then placing that component into the layout of an app. This ensures that the code is loaded to
the client correctly. See example:

- [Bundle component that lists all BYOC components that need to be a part of clientside js](https://github.com/Sitecore/feaas-nextjs-example/blob/main/app/byoc/index.tsx 'https://github.com/Sitecore/feaas-nextjs-example/blob/main/app/byoc/index.tsx')
  \- note that it uses a special kind of import “for side-effects”, that ensures that code will be a part of the bundle
  regardless of if it’s used or not.
- [A place where this bunde is included](https://github.com/Sitecore/feaas-nextjs-example/blob/main/app/layout.tsx#L32 'https://github.com/Sitecore/feaas-nextjs-example/blob/main/app/layout.tsx#L32')

### Rendered on server

Other class of components is rendered on server. This allows the output of the component be indexable by search engines,
and enables fully static websites that have very little clientside javascript.

There are two variations of these:

1.  **Old-style server component -** a one that can not use hooks like useEffect, but can output JSX. All the dynamic
    data fetching must happen on the page level. This is an approach JSS is taking for BYOC components.
2.  **New async server components -** a type of server-only component that can have its own asynchronous logic (e.g.
    fetching data, calling apis, etc), making it very powerful and easy to use. It is a great way to mak make secure
    requests to databases, services and apis without exposing the secrets and credentials to the clientside app. The
    downside is that it requires next.js 13 app router style of application. This will not be available in JSS apps for
    now.
    [Example](https://github.com/Sitecore/feaas-nextjs-example/blob/main/app/byoc/ExampleServersideComponent.tsx 'https://github.com/Sitecore/feaas-nextjs-example/blob/main/app/byoc/ExampleServersideComponent.tsx')

Server components need to be imported somewhere in the app,
[e.g. in layout](https://github.com/Sitecore/feaas-nextjs-example/blob/main/app/layout.tsx#L7-L13 'https://github.com/Sitecore/feaas-nextjs-example/blob/main/app/layout.tsx#L7-L13').
Notice that it’s a side-effect import (i.e. it does not destructure the exports). This is a special way to import that
opts the code out of tree-shaking. This is important, as next.js app does not know which components are used in the
layout tree or feaas component, so it should not try to optimize them away.

### Hybrid component

A component that renders on server and later gets hydrated on the clientside is pretty popular too. It allows the page
to be indexable, and it makes user see parts of the design before app is fully initialized. It’s a good idea to try to
use this style of components wherever possible. For this to work, the component **needs to be imported both in**
[**client side**](https://github.com/Sitecore/feaas-nextjs-example/blob/main/app/byoc/index.tsx#L10 'https://github.com/Sitecore/feaas-nextjs-example/blob/main/app/byoc/index.tsx#L10')
**and**
[**server side**](https://github.com/Sitecore/feaas-nextjs-example/blob/main/app/layout.tsx#L9 'https://github.com/Sitecore/feaas-nextjs-example/blob/main/app/layout.tsx#L9')
of the next.js bundle.
[Example](https://github.com/Sitecore/feaas-nextjs-example/blob/main/app/byoc/ExampleHybridComponent.tsx 'https://github.com/Sitecore/feaas-nextjs-example/blob/main/app/byoc/ExampleHybridComponent.tsx')

### Swappable component

Another way to combine two components is that server side renders something different from the clientside. It could be a
placeholder, or empty state of a component, that gets replaced with a interactive one as soon as the page loads. Since
it’s a variation of hybrid omponent, it also requires registering 2 components, one on the server and one on the
clientside.
[Example](https://github.com/Sitecore/feaas-nextjs-example/blob/main/app/byoc/ExampleSwappedComponent.tsx 'https://github.com/Sitecore/feaas-nextjs-example/blob/main/app/byoc/ExampleSwappedComponent.tsx')

### Wrapper component

In apps that support app router and async components, it is possible to create combinations of async and sync
components. For
[example](https://github.com/Sitecore/feaas-nextjs-example/blob/main/app/byoc/ExampleWrapperComponent.tsx 'https://github.com/Sitecore/feaas-nextjs-example/blob/main/app/byoc/ExampleWrapperComponent.tsx')
async server component fetches data and passes it to clientside component to do something with it. Since there’s server
and client component involved, it requires each of those to be registered on server and in client side bundles
separately.

## Guidelines

#### 1. Each component *must* be exported individually:

This is so users can choose what they want to use.

```typescript
// Somewhere in the user app
import '@sitecore/components/form'
import '@sitecore/components/search'
```

Keep in mind this special `side effects` import syntax, which opts out of code tree-shaking. It means the components
_will_ be included in the user app, regardless of if they are used in the page or not. The reason for this is that XM
and FEAAS components both are rendered dynamically, so the tree-shaking algorithm can not possibly do a good job.

#### 2. There's no bundling, only typescript transpilation

This is to avoid double bundling of different versions of the shared libraries. Adding dependencies to the package is
OK. All of the dependencies will be installed into the user app, but they wont be bundled into their code unless
component is actually used. This is why it is important

Keep dependencies to the minimum to avoid bloat.

#### 3. Components can be rendered directly

Usually BYOC components are rendered as a part of XM page or FEAAS component, so there's no need to refer to them
directly. But if there's a need to render a component manually (e.g. in Layout of an app), it can be possible to render
the the components directly. However it's important to keep the side-effect import in place.

Example of rendering a component directly:

```typescript
// keep the side-effect import to ensure the unconditional bundling
import '@sitecore/components/form'
// destructure as a separate import
import {Form} '@sitecore/components/form'
// use it in the app
;<Form formId='my-form-id' />
```

Example of rendering a component through a wrapper:

```typescript
// import the necessary component
import '@sitecore/components/form'
// import BYOC runtime
import * as BYOC from '@sitecore-feaas/byoc'
// use it in the app
;<BYOC.Component componentName='form' formId='my-form-id' />
```

### Authoring

See
[@sitecore-feaas/clientside](https://www.npmjs.com/package/@sitecore-feaas/clientside#user-content-bring-your-own-code-components)
readme for more information about component registration under Bring your own components section.

## BYOD(Bring your own data) - registerDatasource

BYDO allows developers to register datasource definitions to be used in Pages and Components app. Datasources defined
this way will act the same way as regular datasources created in Components Builder UI. In addition, it is possible to
use BYOD mechanism to intercept and customize datasources created in the UI.

The `registerDatasource` function is a key part of the `@sitecore/byoc` package. It allows you to register a data source
that can be used by your application.

### Usage

First, import the `registerDatasource` function from the `@sitecore/byoc` package:

```javascript
import { registerDatasource } from '@sitecore/byoc'
```

Then, you can use the `registerDatasource` function to register a data source. The function takes two arguments:

- `handler`: A function that customizes the settings of the data source when called from a `RegisteredDatasource`. This
  function can return either DataSettings for HTTP request or a Promise to return the data.
- `options: DatasourceOptionsInput`: An object with the following properties:
  - `id`: A unique identifier for the data source.
  - `description`: (Optional) A textual description to be displayed in UI
  - `name`: (Optional) The name of the data source.
  - `schema`: (Optional) The JSON schema for the data source.
  - `sample`: (Optional) Sample data for the data source.
  - `properties: (Optional) Alternative way to provide schema is to provide `properties` directly

#### Example 1: HTTP-based datasource, described by schema

Handler function returns DataSettings detailing the fetch request. The options object is a JSON schema describing the
response.

```typescript
registerDatasource(
  () => ({
    // DataSettings is `url`, `headers`, `params`, `method` and `body`.
    url: 'https://api.sampleapis.com/wines/reds'
  }),
  {
    id: 'http-and-schema',
    name: 'Wines via HTTP',
    description: 'List of red wines fetched by HTTP, each with `wine`, `price` and `id` property',
    // options object is JSON-schema compatible
    type: 'array',
    properties: {
      wine: { type: 'string' },
      price: { type: 'string' },
      id: { type: 'number' }
    }
  }
)
```

#### Example 2: Customizing data source settings

When `registerDatasource` is given an ID of a datasource that exist in the library (i.e. created via UI), the handler
can adjust the data settings of the original request.

```typescript
registerDatasource(
  // settings will contain DataSettings as specified via UI in Components app
  (settings) => ({
    ...settings,
    params: {
      // add ?page=2 parameter to the original URL
      ...settings.params,
      page: 2
    },
    headers: {
      // add Authorization header in addition to original headers
      ...settings.headers,
      Authorization: 'Bearer token'
    }
  }),
  {
    // ID of a datasource as created in UI (can be visible in the address bar URL) to be extended
    // No other options need to be specified for this case of intercepting datasource.
    id: 'aBcDaaa23a'
  }
)
```

#### Example 3: Reading JSON from file, with sample data.

When handler returns promise (e.g. if it's async function), the promised data is passed to component directly bypassing
the original HTTP request. This allows returning data through alternative means, like reading from a file, database,
etc.

```typescript
import { promises as fs } from 'fs'
// Async handlers supposed to return data itself instead of DataSettings
registerDatasource(
  async () => {
    return JSON.parse(await fs.readFile('wines.json', 'utf-8'))
  },
  {
    id: 'file-and-sample',
    name: 'Wines from JSON file',
    description: 'JSON file read and parsed from file (no HTTP request is made), with sample data',
    // Instead of JSON schema, sample data can be provided directly.
    sample: [
      { wine: 'Emporda 2012', id: 1, price: '$250' },
      { wine: 'Pêra-Manca Tinto 1990', id: 2, price: '$312' }
    ]
  }
)
```

#### More on `schema` and `sample` options

- if a `sample` was provided in the options it assigns it to the sample property of `RegisteredDatasource` as is. The
  user will be able to map their components against that sample data.
- if a `schema` was provided in the options argument of `registerDatasource` it assigns it to the `schema` property of
  the `RegisteredDatasource`, so that the UI can generate sample automatically if it's not provided.
- If a `properties` object was provided in the options of `registerDatasource`, it's added to the `schema` of the
  `RegisteredDatasource`
- Finally, if a title was provided in the datasourceOptions, it's added to the schema of the `RegisteredDatasource`.
- If neither sample or schema were provided, the datasource is considered to be in extension mode and will not be
  displayed in UI by itself.
