feat(tools): add component generation script to ui-components (#44951)
* feat(tools): add component generation script to ui-components * Yes, Oliver. I do want to use TS for these
This commit is contained in:
@ -60,6 +60,7 @@
|
|||||||
"build-storybook": "build-storybook",
|
"build-storybook": "build-storybook",
|
||||||
"build": "cross-env NODE_ENV=production rollup -c",
|
"build": "cross-env NODE_ENV=production rollup -c",
|
||||||
"dev": "cross-env NODE_ENV=development rollup -c -w",
|
"dev": "cross-env NODE_ENV=development rollup -c -w",
|
||||||
"clean": "rimraf dist/*"
|
"clean": "rimraf dist/*",
|
||||||
|
"gen-component": "ts-node ./utils/gen-component-script"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,5 +10,11 @@
|
|||||||
"strict": true,
|
"strict": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"typeRoots": ["../../node_modules/@types"]
|
"typeRoots": ["../../node_modules/@types"]
|
||||||
|
},
|
||||||
|
"ts-node": {
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs"
|
||||||
|
},
|
||||||
|
"transpileOnly": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
70
tools/ui-components/utils/gen-component-script.ts
Normal file
70
tools/ui-components/utils/gen-component-script.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { component, story, test, barrel, type } from './gen-component-template';
|
||||||
|
|
||||||
|
// Grab component name from terminal argument
|
||||||
|
const [name] = process.argv.slice(2);
|
||||||
|
if (!name) {
|
||||||
|
throw new Error('You must include a component name.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!name.match(/^[A-Z]/)) {
|
||||||
|
throw new Error('Component name must be in PascalCase.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const toKebabCase = (pascalCasedName: string) =>
|
||||||
|
pascalCasedName
|
||||||
|
.replace(/([A-Z][a-z])/g, '-$1') // Add a hyphen before each capital letter
|
||||||
|
.toLowerCase()
|
||||||
|
.substring(1); // Return the string but exclude the hyphen at the beginning
|
||||||
|
|
||||||
|
const kebabCasedName = toKebabCase(name);
|
||||||
|
|
||||||
|
const dir = path.join(__dirname, `../src/${kebabCasedName}`);
|
||||||
|
|
||||||
|
// Throw an error if the component's folder already exists
|
||||||
|
if (fs.existsSync(dir)) {
|
||||||
|
throw new Error('A component with that name already exists.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the folder
|
||||||
|
fs.mkdirSync(dir);
|
||||||
|
|
||||||
|
const writeFileErrorHandler = (err: Error | null) => {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the component file - my-component.tsx
|
||||||
|
fs.writeFile(
|
||||||
|
`${dir}/${kebabCasedName}.tsx`,
|
||||||
|
component(name),
|
||||||
|
writeFileErrorHandler
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create the type file - types.ts
|
||||||
|
fs.writeFile(`${dir}/types.ts`, type(name), writeFileErrorHandler);
|
||||||
|
|
||||||
|
// Create the test file - my-component.test.tsx
|
||||||
|
fs.writeFile(
|
||||||
|
`${dir}/${kebabCasedName}.test.tsx`,
|
||||||
|
test(name),
|
||||||
|
writeFileErrorHandler
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create the Storybook file - my-component.stories.tsx
|
||||||
|
fs.writeFile(
|
||||||
|
`${dir}/${kebabCasedName}.stories.tsx`,
|
||||||
|
story(name),
|
||||||
|
writeFileErrorHandler
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create the barrel file - index.ts
|
||||||
|
fs.writeFile(
|
||||||
|
`${dir}/index.ts`,
|
||||||
|
barrel(name, kebabCasedName),
|
||||||
|
writeFileErrorHandler
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`The ${name} component has been created successfully! 🎉`);
|
59
tools/ui-components/utils/gen-component-template.ts
Normal file
59
tools/ui-components/utils/gen-component-template.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// component.tsx
|
||||||
|
export const component = (name: string) => `
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { ${name}Props } from './types';
|
||||||
|
|
||||||
|
export const ${name} = ({}: ${name}Props) => {
|
||||||
|
return <div>Hello, I am a ${name} component</div>;
|
||||||
|
};
|
||||||
|
`;
|
||||||
|
|
||||||
|
// types.ts
|
||||||
|
export const type = (name: string) => `
|
||||||
|
export interface ${name}Props {
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// component.test.tsx
|
||||||
|
export const test = (name: string) => `
|
||||||
|
import React from 'react';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
|
import { ${name} } from '.';
|
||||||
|
|
||||||
|
describe('<${name} />', () => {
|
||||||
|
it('should render correctly', () => {});
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
// component.stories.tsx
|
||||||
|
export const story = (name: string) => `
|
||||||
|
import React from 'react';
|
||||||
|
import { Story } from '@storybook/react';
|
||||||
|
import { ${name}, ${name}Props } from '.';
|
||||||
|
|
||||||
|
const story = {
|
||||||
|
title: 'Example/${name}',
|
||||||
|
component: ${name}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Template: Story<${name}Props> = args => {
|
||||||
|
return <${name} {...args} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Default = Template.bind({});
|
||||||
|
Default.args = {
|
||||||
|
// default props go here
|
||||||
|
};
|
||||||
|
|
||||||
|
export default story;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// index.ts
|
||||||
|
export const barrel = (name: string, kebabCasedName: string) => `
|
||||||
|
export { ${name} } from './${kebabCasedName}';
|
||||||
|
export type { ${name}Props } from './types';
|
||||||
|
`;
|
Reference in New Issue
Block a user