#headless#javascript#jss#scaffolding#sitecore#sxa#xm-cloud#sitecore jss

Next.js Scaffolding Script: Simplifying Component Setup and Configuration

June 23, 2024·5 min read

What is Scaffolding?

Scaffolding is the process of generating a component based on predefined templates. Typically, when we create a new component, we start from scratch and follow the same repetitive steps each time. This approach can be time-consuming. Scaffolding, on the other hand, provides a basic template with essential code, saving you the effort of rewriting the same code repeatedly.

How Can We Achieve Scaffolding?

Sitecore JSS provides an out-of-the-box (OOTB) Component Scaffolding feature, but it has some limitations. By leveraging the code provided by Sitecore, we can enhance the scaffolding to include additional files, such as Storybook and mock-data files, when creating a scaffolded component. With some configurations, we can also generate various other files.

To create a new component using scaffolding, use the following command:

typescript
jss scaffold ComponentName

or

typescript
npm run scaffold ComponentName

Scaffolding Script

You can find the scaffolding script in your Next.js src directory:

typescript
/src/[project_name]/scripts/scaffold-component/index.ts

Inside the index.ts file, the first step is to define the component name format. You can modify the regular expression based on your requirements:

typescript
const nameParamFormat = new RegExp(/^((?:[\\\\w\\\\-]+\\\\/)*)([A-Z][\\\\w-]+)$/);

This means component names should start with a capital letter and contain only letters, numbers, underscores, or dashes.

typescript
const componentArg = process.argv[2];
const args = process.argv.slice(3);
typescript
// These lines will process the arguments provided in the CLI command.

Next, specify the default configuration, including the component template, component path, and component name.

typescript
const defaultConfig: ScaffoldComponentPluginConfig = {
  componentPath: regExResult[1],
  componentName: regExResult[2],
  componentTemplateGenerator: generateComponentSrc,
  args: args,
  nextSteps: [],
};

generateComponentSrc is the template name imported from another file.

typescript
import generateComponentSrc from 'scripts/templates/component-src';

This is the location of the component template (you can create this file in another location if needed). Finally, you’ll find the implementation of the component execution config.

typescript
const config = (Object.values(plugins) as ScaffoldComponentPlugin[])
  .sort((p1, p2) => p1.order - p2.order)
  .reduce((config, plugin) => plugin.exec(config), defaultConfig);

Component Script

Inside the /src/[project_name]/scripts/scaffold-component/plugins directory, you’ll find the component.ts file, which includes the component configuration.

typescript
import path from 'path';
import { scaffoldFile } from '@sitecore-jss/sitecore-jss-dev-tools';
import { ScaffoldComponentPlugin, ScaffoldComponentPluginConfig } from '..';
class ComponentPlugin implements ScaffoldComponentPlugin {
  order = 99;
 exec(config: ScaffoldComponentPluginConfig) {
    const { componentName, componentPath } = config;
    const filename = `${componentName}.tsx`;
    const componentRoot = componentPath.startsWith('src/') ? '' : 'src/components/' + componentName;
    const outputFilePath = path.join(componentRoot, componentPath, filename);
    const template = config.componentTemplateGenerator(componentName);
    const componentOutputPath = scaffoldFile(outputFilePath, template);
    return {
      ...config,
      componentOutputPath,
    };
  }
}
export const componentPlugin = new ComponentPlugin();

This script retrieves the component name and path from the config file, determines the output directory, and uses the scaffoldFile function provided by Sitecore JSS to generate the component based on the template.

Component Template File

Inside the src/[project_name]/scripts/templates directory, you’ll find the component-src.ts file where you can define your component snippet. You can also use the OOTB file. Here’s an example that also includes a Tailwind variant snippet:

typescript
function generateComponentSrc(componentName: string): string {
const component = componentName.charAt(0).toLowerCase() + componentName.slice(1);
return `import React from 'react';
import { withDatasourceCheck } from '@sitecore-jss/sitecore-jss-nextjs';
import { tv } from 'tailwind-variants';
import { ComponentProps } from 'lib/component-props';

export type ${componentName}Props = ComponentProps & {
  fields: unknown;
};

const ${component}Variants = tv({
  slots: {
    base: ['${component}'],
  },
  compoundSlots: [{ slots: [], class: [] }],
  variants: {
    size: {
      mobile: {},
      desktop: {},
    },
  },
}, { responsiveVariants: ['lg'] });
const ${componentName}: React.FC<${componentName}Props> = ({ fields, params }) => {
  const { base } = ${component}Variants({ size: { initial: 'mobile', lg: 'desktop' } });
  if (!fields) return <></>;
  return (
    <div className={base({ className: params?.Style ?? '' })}>
      ${componentName}
    </div>
  );
};
export default withDatasourceCheck()<${componentName}Props>(${componentName});
`;
}
export default generateComponentSrc;

Adding Storybook and Mock-Data Files

If your project requires story book then here the example which describe how to scaffold these files also using the scaffold command.

To add Storybook and mock-data files, first create two new templates in the src/[project_name]/scripts/templates directory:

  1. story-src.ts
  2. mock-src.ts

story-src.ts

typescript
function generateStorySrc(componentName: string): string {
return `import type { Meta, StoryObj } from '@storybook/react';
import ${componentName}, { ${componentName}Props } from '../../../components/${componentName}/${componentName}';
import defaultData from './${componentName}.mock-data';
const meta: Meta<typeof ${componentName}> = {
  title: 'Components/${componentName}',
  component: ${componentName},
  tags: ['autodocs'],
  argTypes: {},
};
export default meta;
type Story = StoryObj<typeof ${componentName}>;
export const Default: Story = {
  render: (args) => {
    return <${componentName} {...args} />;
  },
  args: defaultData,
};
`;
}
export default generateStorySrc;

mock-src.ts

typescript
function generateMockSrc(componentName: string): string {
return `import { ${componentName}Props } from 'components/${componentName}/${componentName}';const defaultData: ${componentName}Props = {
  rendering: {
    componentName: '${componentName}',
    dataSource: '{00000000-0000-0000-0000-000000000000}',
  },
  params: {},
  fields: {},
};
export default defaultData;
`;
}
export default generateMockSrc;

Next, create two files similar to component.ts inside the plugins folder, configuring these files with the template path and output directory path. For an example, mock.ts:

typescript
import path from 'path';
import { scaffoldFile } from '@sitecore-jss/sitecore-jss-dev-tools';
import { ScaffoldComponentPlugin, ScaffoldComponentPluginConfig } from '..';
import generateMockSrc from 'scripts/templates/mock-src';
class MockPlugin implements ScaffoldComponentPlugin {
  order = 80;
  exec(config: ScaffoldComponentPluginConfig) {
    const { componentName, componentPath } = config;
    const filename = `${componentName}.mock-data.ts`;
    const componentRoot = componentPath.startsWith('src/')
      ? ''
      : 'src/stories/components/' + componentName;
    const outputFilePath = path.join(componentRoot, componentPath, filename);
    const componentOutputPath = scaffoldFile(outputFilePath, generateMockSrc(componentName));
    return {
      ...config,
      componentOutputPath,
    };
  }
}
export const mockPlugin = new MockPlugin();
typescript
Create a similar file for Storybook, changing the output destination and template imports.

Finally, register your new .ts files in your index.ts file:

typescript
const storyConfig: ScaffoldComponentPluginConfig = {
  componentPath: regExResult[1],
  componentName: regExResult[2],
  componentTemplateGenerator: generateStorySrc,
  args: args,
  nextSteps: [],
};

storyConfig.nextSteps.push(
  chalk.green(`
Scaffolding of ${storyConfig.componentName} complete.
Next steps:`)
);

const mockConfig: ScaffoldComponentPluginConfig = {
  componentPath: regExResult[1],
  componentName: regExResult[2],
  componentTemplateGenerator: generateMockSrc,
  args: args,
  nextSteps: [],
};

mockConfig.nextSteps.push(
  chalk.green(`
Scaffolding of ${mockConfig.componentName} complete.
Next steps:`)
);

Add the above configuration inside the index.ts file. The nextSteps property provides feedback to confirm successful file creation.

Congratulations! You’ve now created your own scaffolding files and can customize them further to suit your needs.