npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@sitecore/byoc

v0.2.16

Published

Bring-Your-Own-Components runtime to register and retrieve react components

Downloads

88,256

Readme

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). 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:

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

Server components need to be imported somewhere in the app, e.g. in layout. 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 and server side of the next.js bundle. Example

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

Wrapper component

In apps that support app router and async components, it is possible to create combinations of async and sync components. For example 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.

// 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:

// 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:

// 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 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:

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.

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.

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.

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.