We’ve talked about Sitecore’s OOTB Component Scaffolding on our last blog Improve Development using Sitecore JSS Component Scaffolding in Next js projects. It introduced us on how we can easily add components into our headless project without having to manually create the files for our components.
There are some limitations to how useful the base code gives us, and it would be great if we can put our own personal or company’s touch into it which can even improve it to a greater degree. Let’s get down to business and talk about how the script actually works and how we can improve it.
The Scaffolding Script
Directly under your Next.js project folder you will see the scripts
folder which hold a lot of the vital scripts we need for our frontend to work with Sitecore. In it, you will see the scaffold-component
folder which contains all the code that runs the Component Scaffolding command, it might be a bit intimidating but we’ll break the code into parts and get a better understanding of it.
First we’ll check the index.ts
this is where all of the pieces are put together in order to get the Scaffolding to work. Let’s try to connect the dots between what we entered on the terminal and where it’s going on the script.
npm run scaffold src/components/generics/ComponentName
On Line 52, we have a regular expression that takes the path section of the command and the component and separates them into two.
const nameParamFormat = new RegExp(/^((?:[\w\-]+\/)*)([A-Z][\w-]+)$/);
On Line 60, it uses this regular expression and passes in the argument entered on the command line, which will be src/components/generics/ComponentName
, we can also pass in the component itself and it will keep the path an empty string.
const regExResult = nameParamFormat.exec(componentArg);
Lines 67-73 are the most important of that file. These lines are responsible of the object that is being passed into each of the functions or plugins.
const defaultConfig: ScaffoldComponentPluginConfig = {
componentPath: regExResult[1],
componentName: regExResult[2],
componentTemplateGenerator: generateComponentSrc,
args: args,
nextSteps: [],
};
The first two keys comes from the parsed text using the regex, componentPath
will have the first string which is the path and if you’ve entered just the component name then it will be an empty string. Then componentName
will store the 2nd part of the regular expression which contains the Component’s name. The last important bit is componentTemplateGenerator
, which contains a function imported into the file which can be located at 'scripts/templates/component-src'
. We’ll come back to this function later on. The rest of the code basically runs the plugins
folder. The important part for generating our component is at scaffold-component/plugins/component.ts
.
The Component Script
We’ll take a look at the file inside /scripts/caffold-component/plugins/component.ts
.
The code inside is pretty straightforward, we’ll have to take a look at the Lines 12-23.
const { componentName, componentPath } = config;
const filename = `${componentName}.tsx`;
const componentRoot = componentPath.startsWith('src/') ? '' : 'src/components';
const outputFilePath = path.join(componentRoot, componentPath, filename);
const template = config.componentTemplateGenerator(componentName);
const componentOutputPath = scaffoldFile(outputFilePath, template);
return {
...config,
componentOutputPath,
};
The 2nd line is responsible of the filename used for the rest of the script.
const filename = `${componentName}.tsx`;
The 3rd line generates the path where the file is created, an empty path will be automatically generated at src/components
.
const componentRoot = componentPath.startsWith('src/') ? '' : 'src/components';
The 4th line is the final formatted path on where the component template will be pasted.
const outputFilePath = path.join(componentRoot, componentPath, filename);
The 5th line uses the template generator function seen on the index.ts
file. This part is useful for any creative way to improve our component boilerplate and add any initial requirements the team or you personally need on every component.
const template = config.componentTemplateGenerator(componentName);
The next line uses a function developed by Sitecore which does the file generation.
const componentOutputPath = scaffoldFile(outputFilePath, template);
The componentOutputPath
is then passed on with the initial config which will be used on the succeeding plugins which are mostly used to show on the terminal the progress of the file generation.
The Template File
The template file which is responsible for the component boilerplate can be located at scripts/templates/component-src.ts
. Any customization can be added here, you can do some experimenting on how much you can push the boilerplate to get a even more useful generic component template. The basic boilerplate will look like the code below.
/**
* Generates React boilerplate for a component under `src/components`
* @param componentName - the component name
* @returns component src boilerplate as a string
*/
function generateComponentSrc(componentName: string): string {
return `import React from 'react';
import { ComponentParams, ComponentRendering } from '@sitecore-jss/sitecore-jss-nextjs';
interface ${componentName}Props {
rendering: ComponentRendering & { params: ComponentParams };
params: ComponentParams;
}
export const Default = (props: ${componentName}Props): JSX.Element => {
const id = props.params.RenderingIdentifier;
return (
<div className={\`component \${props.params.styles}\`} id={id ? id : undefined}>
<div className="component-content">
<p>${componentName} Component</p>
</div>
</div>
);
};
`;
}
export default generateComponentSrc;
Customizing the Component Scaffolding
Sitecore doesn't have visibility into how each team or developer configures their components, as each of us follows our own set of steps for creating files and determining the specific code requirements for each. However, the provided scripts, we can expand upon and customize our implementations to align with our desired aesthetic and functionality.
One great example of how we can improve the Scaffolding is by adding in the Storybook file needed for each component. I’ve whipped up some boilerplate code we can use for it. Let us add a new file inside the templates
folder and let’s name it component-story.ts
/**
* Generates React boilerplate for a component under `src/components`
* @param componentName - the component name
* @returns component src boilerplate as a string
*/
function generateComponentStory(componentName: string): string {
return `import type { Meta, StoryObj } from '@storybook/react';
import { Default as ${componentName} } from 'components/${componentName}';
const meta: Meta<typeof ${componentName}> = {
title: 'Components/${componentName}',
component: ${componentName},
tags: ['autodocs'],
};
export default meta;
type Story = StoryObj<typeof ${componentName}>;
export const Default: Story = {
render: (args) => (
<${componentName}
{...args}
rendering={{
componentName: '${componentName}',
dataSource: '/sitecore',
params: {
GridParameters: 'col-12',
DynamicPlaceholderId: '1',
FieldNames: 'Default',
},
}}
/>
),
};
`;
}
export default generateComponentStory;
Next we will need to integrate generating this file into the script. Let’s first pass in the Storybook Template Generator into the defaultConfig
inside the index.ts
file. We’ll need this for the code inside our storybook component that we’ll need to add in a the new storybook
plugin. Don’t forget to update the typescript in order to prevent any typescript errors for storybookTemplateGenerator
.
const defaultConfig: ScaffoldComponentPluginConfig = {
componentPath: regExResult[1],
componentName: regExResult[2],
componentTemplateGenerator: generateComponentSrc,
storybookTemplateGenerator: generateComponentStory,
args: args,
nextSteps: [],
};
Let’s duplicate the component.ts
file and rename it componentstory.ts
. I named it in order for the file to sit exactly below the component file. You don’t have to do the same thing if the file name doesn’t fit you. After a few revisions the script will look like this.
import path from 'path';
import { scaffoldFile } from '@sitecore-jss/sitecore-jss-dev-tools';
import { ScaffoldComponentPlugin, ScaffoldComponentPluginConfig } from '..';
/**
* Generates the component file.
*/
class ComponentStoryPlugin implements ScaffoldComponentPlugin {
order = 99;
exec(config: ScaffoldComponentPluginConfig) {
const { componentName } = config;
const filename = `${componentName}.stories.tsx`;
const componentRoot = 'src/stories/components/';
const outputFilePath = path.join(componentRoot, filename);
const template = config.storybookTemplateGenerator(componentName);
const componentStoryOutputPath = scaffoldFile(outputFilePath, template);
return {
...config,
componentStoryOutputPath,
};
}
}
export const componentstoryPlugin = new ComponentStoryPlugin();
It will look very similar to the component.ts
file, here are the following changes I’ve introduced into the file:
- Path for the file will be fixed into the stories folder.
- Change the
filename
in order for it to become a storybook component. - Utilize the
storybookTemplateGenerator
function we’ve created for the template used for this plugin.
Maximizing Efficiency With Enhanced Component Scaffolding
We’ve looked into the different files involved in Scaffolding our component and the boilerplate used for every new component. You now have the power to tinker around and optimize the boilerplate and reduce even more development required for a new component setup.
We’ve also talked about pushing the Scaffolding’s usefulness by adding in a new plugin responsible of generating the storybook component for every new component. Now you’ll be able to think about what other file is required for every new component you introduce to your project. This will cut a new minutes off every new component.