Created
February 28, 2023 22:23
-
-
Save AdrianFahrbach/9bccd4388be85253bf07435aeb57ddfe to your computer and use it in GitHub Desktop.
Storybook stories generation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { execSync } from 'child_process'; | |
import fs from 'fs'; | |
import { startCase } from 'lodash'; | |
import path from 'path'; | |
const warningMessage = ` | |
/***************************************************************************************************************** | |
* * | |
* This file is autogenerated. If you want to make persistent changes, change the respective .legend.tsx file! * | |
* * | |
****************************************************************************************************************/`; | |
const srcDirectories = ['./libs/components/src/lib/blocks/', './libs/ui/src/lib/']; | |
let legendFiles: string[] = []; | |
const timerLabel = '\x1b[32minfo\x1b[0m => Stories generated in'; | |
console.time(timerLabel); | |
(async () => { | |
// Merge all files in a single array and remove duplicates | |
await Promise.all( | |
srcDirectories.map(async srcDirectory => { | |
const files = fs | |
.readdirSync(srcDirectory) | |
.filter(filename => filename.endsWith('.legend.tsx')) | |
.map(filename => path.join(srcDirectory, filename)); | |
legendFiles = legendFiles.concat(files); | |
}) | |
); | |
legendFiles = [...new Set(legendFiles)]; | |
// Loop through each legend file | |
console.log('\x1b[32minfo\x1b[0m => Generating stories...'); | |
await Promise.all( | |
legendFiles.map(async legendFilePath => { | |
const storiesFilePath = legendFilePath.replace('.legend.tsx', '.generated.stories.tsx'); | |
let tempFileContents = fs.readFileSync(legendFilePath, 'utf-8'); | |
// We don't want the compiled React component. To prevent that we replace | |
// every instance of it with a placeholder string and revert that change later. | |
const spacesBefore = tempFileContents.match(/^(\s+)*render:/m)[1]; | |
const findRenderFunctionRegex = new RegExp(`^${spacesBefore}render:([\\S\\s]*^${spacesBefore}[)}]?,|.+,)`, 'm'); | |
const foundRenderFunction = tempFileContents.match(findRenderFunctionRegex); | |
let renderFunction = ''; | |
if (foundRenderFunction && foundRenderFunction.length > 0) { | |
renderFunction = foundRenderFunction[0]; | |
tempFileContents = tempFileContents.replace(findRenderFunctionRegex, 'render: "%RENDER_PLACEHOLDER%"'); | |
} | |
const findComponentRegex = /component: .+,/; | |
const foundComponent = tempFileContents.match(findComponentRegex); | |
let component = ''; | |
if (foundComponent && foundComponent.length > 0) { | |
component = foundComponent[0]; | |
tempFileContents = tempFileContents.replace(findComponentRegex, 'component: "%COMPONENT_PLACEHOLDER%"'); | |
} | |
fs.writeFileSync(storiesFilePath, tempFileContents); | |
// Get the adjusted file | |
const contents = fs.readFileSync(storiesFilePath, 'utf-8'); | |
const { exportsToGenerate, meta } = await import(storiesFilePath); | |
// Get imports from file because we need those later | |
const foundImports = contents.match(/^import .+';/gm); | |
let imports = ''; | |
if (foundImports && foundImports.length > 0) { | |
imports = foundImports.join('\n'); | |
} | |
// Get meta object and use it as default export | |
let defaultExportString = `export default ${JSON.stringify(meta, null, 2).replace( | |
'"component": "%COMPONENT_PLACEHOLDER%"', | |
component | |
)}`; | |
// Convert exportsToGenerate object to sepatate exports | |
const generatedExports = exportsToGenerate.map((exportsEntry: { [key: string]: any }, index: number) => { | |
const key = startCase(Object.keys(exportsEntry)[0]).replace(/ /g, ''); | |
const value = JSON.stringify(Object.values(exportsEntry)[0], null, 2); | |
let exportsString = `export const ${key} = ${value.endsWith(',') ? value.slice(0, -1) : value}`; | |
exportsString = exportsString.replace('"render": "%RENDER_PLACEHOLDER%"', renderFunction); | |
return exportsString; | |
}); | |
// Construct new story file from different blocks and prettier it | |
const storyFile = | |
warningMessage + '\n\n' + imports + '\n\n' + defaultExportString + '\n\n' + generatedExports.join('\n\n'); | |
// Write the new file contents | |
fs.writeFileSync(storiesFilePath, storyFile); | |
}) | |
); | |
// Organize imports on all files | |
console.log('\x1b[32minfo\x1b[0m => Organizing imports...'); | |
const storiesFiles = legendFiles.map(legendFile => legendFile.replace('.legend.tsx', '.generated.stories.tsx')); | |
execSync('organize-imports-cli ' + storiesFiles.join(' ')); | |
console.log('\x1b[32minfo\x1b[0m => Prettifying stories...'); | |
execSync('prettier --write --list-different ' + storiesFiles.join(' ')); | |
console.timeEnd(timerLabel); | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { schemaToArgTypes } from '@project/services/storybook.service'; | |
import { HeroBlockComponent } from '@project/shared/blocks'; | |
import { Schema } from '@project/shared/cms'; | |
import heroSchema from '@project/shared/cms/schema/components/content/hero.json'; | |
import { Color, colorToColorName } from '@project/shared/colors'; | |
import Hero from './Hero'; | |
import { heroSectionFakeData, HeroThemeColor, heroThemes } from './Hero.config'; | |
export const meta = { | |
title: 'Blocks/Hero', | |
component: Hero, | |
}; | |
export const exportsToGenerate = Object.keys(heroThemes).map(themeColor => ({ | |
[colorToColorName[themeColor as Color]]: { | |
args: { | |
...heroSectionFakeData, | |
backgroundColor: themeColor as HeroThemeColor, | |
} satisfies HeroBlockComponent, | |
argTypes: schemaToArgTypes(heroSchema as Schema, heroSectionFakeData), | |
render: args => <Hero data={args} />, | |
}, | |
})); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "website", | |
"$schema": "../../node_modules/nx/schemas/project-schema.json", | |
"sourceRoot": "apps/website", | |
"projectType": "application", | |
"targets": { | |
"generateStories": { | |
"command": "npx ts-node -O '{\"module\": \"commonjs\"}' -r tsconfig-paths/register apps/website/.storybook/generate-stories.ts" | |
}, | |
"other": "entries...", | |
"storybook": { | |
"executor": "@nrwl/storybook:storybook", | |
"dependsOn": ["copyCmsSchemaFiles", "generateStories"], | |
"options": { | |
"port": 4400, | |
"configDir": "apps/website/.storybook" | |
}, | |
"configurations": { | |
"ci": { | |
"quiet": true | |
} | |
} | |
}, | |
"build-storybook": { | |
"executor": "@nrwl/storybook:build", | |
"dependsOn": ["copyCmsSchemaFiles", "generateStories"], | |
"outputs": ["{options.outputDir}"], | |
"options": { | |
"outputDir": "dist/storybook/website", | |
"configDir": "apps/website/.storybook" | |
}, | |
"configurations": { | |
"ci": { | |
"quiet": true | |
} | |
} | |
} | |
}, | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment