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:
jss scaffold ComponentName
or
npm run scaffold ComponentName
Scaffolding Script
You can find the scaffolding script in your Next.js src directory:
/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:
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.
const componentArg = process.argv[2];
const args = process.argv.slice(3);
// 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.
const defaultConfig: ScaffoldComponentPluginConfig = {
componentPath: regExResult[1],
componentName: regExResult[2],
componentTemplateGenerator: generateComponentSrc,
args: args,
nextSteps: [],
};
generateComponentSrc is the template name imported from another file.
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.
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.
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:
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:
story-src.tsmock-src.ts
story-src.ts
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
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:
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();
Create a similar file for Storybook, changing the output destination and template imports.
Finally, register your new .ts files in your index.ts file:
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.
