Skip to content

Commit

Permalink
add TS support for storybook preview tsx config extension (#9309)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Choudhury <[email protected]>
  • Loading branch information
2 people authored and Tobbe committed Dec 31, 2023
1 parent e585fa1 commit eb70971
Show file tree
Hide file tree
Showing 17 changed files with 120 additions and 75 deletions.
1 change: 1 addition & 0 deletions __fixtures__/test-project/web/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
},
"include": [
"src",
"config",
"../.redwood/types/includes/all-*",
"../.redwood/types/includes/web-*",
"../types",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/setup/i18n/i18nHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ export const handler = async ({ force }) => {
skip: () => fileIncludes(rwPaths.web.storybookConfig, 'withI18n'),
task: async () =>
extendStorybookConfiguration(
path.join(__dirname, 'templates', 'storybook.preview.js.template')
path.join(__dirname, 'templates', 'storybook.preview.tsx.template')
),
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import * as React from 'react'
import { I18nextProvider } from 'react-i18next'
import type { GlobalTypes } from '@storybook/csf'
import type { StoryFn, StoryContext } from '@storybook/react'
import i18n from 'web/src/i18n'

/** @type { import("@storybook/csf").GlobalTypes } */
export const globalTypes = {
export const globalTypes: GlobalTypes = {
locale: {
name: 'Locale',
description: 'Internationalization locale',
Expand All @@ -23,12 +25,10 @@ export const globalTypes = {
* https://github.com/storybookjs/addon-kit/blob/main/src/withGlobals.ts
* Unfortunately that will make eslint complain, so we have to disable it when
* using a hook below
*
* @param { import("@storybook/addons").StoryFn} StoryFn
* @param { import("@storybook/addons").StoryContext} context
* @returns a story wrapped in an I18nextProvider
* @param { import("@storybook/react").StoryFn} StoryFn
* @param { import("@storybook/react").StoryContext} context
*/
const withI18n = (StoryFn, context) => {
const withI18n = (StoryFn: StoryFn, context: StoryContext) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
React.useEffect(() => {
i18n.changeLanguage(context.globals.locale)
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/setup/ui/libraries/chakra-ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export async function handler({ force, install }) {
__dirname,
'..',
'templates',
'chakra.storybook.preview.js.template'
'chakra.storybook.preview.tsx.template'
)
),
},
Expand Down
5 changes: 3 additions & 2 deletions packages/cli/src/commands/setup/ui/libraries/mantine.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,14 +158,15 @@ export async function handler({ force, install, packages }) {
},
{
title: 'Configure Storybook...',
skip: () => fileIncludes(rwPaths.web.storybookConfig, 'withMantine'),
skip: () =>
fileIncludes(rwPaths.web.storybookPreviewConfig, 'withMantine'),
task: async () =>
extendStorybookConfiguration(
path.join(
__dirname,
'..',
'templates',
'mantine.storybook.preview.js.template'
'mantine.storybook.preview.tsx.template'
)
),
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import * as React from 'react'

import { ChakraProvider, extendTheme } from '@chakra-ui/react'
import * as theme from 'config/chakra.config'
import type { StoryFn } from '@storybook/react'
import theme from 'config/chakra.config'

const extendedTheme = extendTheme(theme)

const withChakra = (StoryFn) => {
/**
* @param { import("@storybook/react").StoryFn} StoryFn
*/
const withChakra = (StoryFn: StoryFn) => {
return (
<ChakraProvider theme={extendedTheme}>
<StoryFn />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import * as React from 'react'

import { MantineProvider } from '@mantine/core'
import type { StoryFn } from '@storybook/react'
import theme from 'config/mantine.config'

import '@mantine/core/styles.css'

const withMantine = (StoryFn) => {
/**
* @param { import("@storybook/react").StoryFn} StoryFn
*/
const withMantine = (StoryFn: StoryFn) => {
return (
<MantineProvider theme={theme}>
<StoryFn />
Expand Down
30 changes: 30 additions & 0 deletions packages/cli/src/lib/__tests__/mergeBasics.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,36 @@ describe('the basics', () => {
{ ArrayExpression: concatUnique }
)
})
it('Merges JSX strings', () => {
const componentA = 'const ComponentA = (props) => <div>Hello</div>'
const componentB = 'const ComponentB = (props) => <div>Bye</div>'
expectTrivialConcat(componentA, componentB)
})
it('Merges TSX strings', () => {
const componentA =
'const ComponentA: MyComponent = (props) => <div>Hello</div>'
const componentB =
'const ComponentB: MyComponent = (props) => <div>Bye</div>'
expectTrivialConcat(componentA, componentB)
})
it('Merges TS strings', () => {
expectMerged(
`\
const x: string = 'x'
const list: string[] = [x]
`,
`\
const y: string = 'y'
const list: string[] = [y]
`,
`\
const x: string = 'x'
const y: string = 'y'
const list: string[] = [x, y]
`,
{ ArrayExpression: concatUnique }
)
})
})

describe('Import behavior', () => {
Expand Down
41 changes: 31 additions & 10 deletions packages/cli/src/lib/configureStorybook.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import path from 'path'
import util from 'util'

import fse from 'fs-extra'
import prettier from 'prettier'
Expand All @@ -11,24 +10,46 @@ import {
keepBoth,
keepBothStatementParents,
} from './merge/strategy'
import { isTypeScriptProject } from './project'

import { getPaths } from '.'
import { getPaths, transformTSToJS, writeFile } from '.'

/**
* Extends the Storybook configuration file with the new configuration file
* @param {string} newConfigPath - The path to the new configuration file
*/
export default async function extendStorybookConfiguration(
newConfigPath = undefined
) {
const sbPreviewConfigPath = getPaths().web.storybookPreviewConfig
const webPaths = getPaths().web
const ts = isTypeScriptProject()
const sbPreviewConfigPath =
webPaths.storybookPreviewConfig ??
`${webPaths.config}/storybook.preview.${ts ? 'tsx' : 'js'}`
const read = (path) => fse.readFileSync(path, { encoding: 'utf-8' })

if (!fse.existsSync(sbPreviewConfigPath)) {
await util.promisify(fse.cp)(
path.join(__dirname, 'templates', 'storybook.preview.js.template'),
sbPreviewConfigPath
// If the Storybook preview config file doesn't exist, create it from the template
const templateContent = read(
path.resolve(__dirname, 'templates', 'storybook.preview.tsx.template')
)
const storybookPreviewContent = ts
? templateContent
: transformTSToJS(sbPreviewConfigPath, templateContent)

await writeFile(sbPreviewConfigPath, storybookPreviewContent)
}

const storybookPreviewContent = read(sbPreviewConfigPath)

if (newConfigPath) {
const read = (path) => fse.readFileSync(path, { encoding: 'utf-8' })
const write = (path, data) => fse.writeFileSync(path, data)
const merged = merge(read(sbPreviewConfigPath), read(newConfigPath), {
// If the new config file path is provided, merge it with the Storybook preview config file
const newConfigTemplate = read(newConfigPath)
const newConfigContent = ts
? newConfigTemplate
: transformTSToJS(newConfigPath, newConfigTemplate)

const merged = merge(storybookPreviewContent, newConfigContent, {
ImportDeclaration: interleave,
ArrayExpression: concatUnique,
ObjectExpression: concatUnique,
Expand All @@ -41,6 +62,6 @@ export default async function extendStorybookConfiguration(
...(await prettier.resolveConfig(sbPreviewConfigPath)),
})

write(sbPreviewConfigPath, formatted)
writeFile(sbPreviewConfigPath, formatted, { overwriteExisting: true })
}
}
3 changes: 2 additions & 1 deletion packages/cli/src/lib/merge/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ function mergeAST(baseAST, extAST, strategy = {}) {
export function merge(base, extension, strategy) {
function parseReact(code) {
return parse(code, {
presets: ['@babel/preset-react'],
filename: 'merged.tsx', // required to prevent babel error. The .tsx is relevant
presets: ['@babel/preset-typescript'],
})
}

Expand Down
16 changes: 0 additions & 16 deletions packages/cli/src/lib/templates/storybook.preview.js.template

This file was deleted.

18 changes: 18 additions & 0 deletions packages/cli/src/lib/templates/storybook.preview.tsx.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react'

import type { GlobalTypes } from '@storybook/csf'
import type { StoryFn, StoryContext } from '@storybook/react'

/** @type { import("@storybook/csf").GlobalTypes } */
export const globalTypes: GlobalTypes = {}

/**
* An example, no-op storybook decorator. Use a function like this to create decorators.
* @param { import("@storybook/react").StoryFn} StoryFn
* @param { import("@storybook/react").StoryContext} context
*/
const _exampleDecorator = (StoryFn: StoryFn, _context: StoryContext) => {
return <StoryFn />
}

export const decorators = []
1 change: 1 addition & 0 deletions packages/create-redwood-app/templates/js/web/jsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
},
"include": [
"src",
"config",
"../.redwood/types/includes/all-*",
"../.redwood/types/includes/web-*",
"../types",
Expand Down
1 change: 1 addition & 0 deletions packages/create-redwood-app/templates/ts/web/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
},
"include": [
"src",
"config",
"../.redwood/types/includes/all-*",
"../.redwood/types/includes/web-*",
"../types",
Expand Down
28 changes: 4 additions & 24 deletions packages/project-config/src/__tests__/paths.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,7 @@ describe('paths', () => {
'config',
'storybook.config.js'
),
storybookPreviewConfig: path.join(
FIXTURE_BASEDIR,
'web',
'config',
'storybook.preview.js'
),
storybookPreviewConfig: null,
storybookManagerConfig: path.join(
FIXTURE_BASEDIR,
'web',
Expand Down Expand Up @@ -363,12 +358,7 @@ describe('paths', () => {
'config',
'storybook.config.js'
),
storybookPreviewConfig: path.join(
FIXTURE_BASEDIR,
'web',
'config',
'storybook.preview.js'
),
storybookPreviewConfig: null,
storybookManagerConfig: path.join(
FIXTURE_BASEDIR,
'web',
Expand Down Expand Up @@ -649,12 +639,7 @@ describe('paths', () => {
'config',
'storybook.config.js'
),
storybookPreviewConfig: path.join(
FIXTURE_BASEDIR,
'web',
'config',
'storybook.preview.js'
),
storybookPreviewConfig: null,
storybookManagerConfig: path.join(
FIXTURE_BASEDIR,
'web',
Expand Down Expand Up @@ -892,12 +877,7 @@ describe('paths', () => {
'config',
'storybook.config.js'
),
storybookPreviewConfig: path.join(
FIXTURE_BASEDIR,
'web',
'config',
'storybook.preview.js'
),
storybookPreviewConfig: null,
storybookManagerConfig: path.join(
FIXTURE_BASEDIR,
'web',
Expand Down
10 changes: 4 additions & 6 deletions packages/project-config/src/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export interface WebPaths {
entryClient: string | null
postcss: string
storybookConfig: string
storybookPreviewConfig: string
storybookPreviewConfig: string | null
storybookManagerConfig: string
dist: string
types: string
Expand Down Expand Up @@ -105,9 +105,8 @@ const PATH_WEB_DIR_ENTRY_CLIENT = 'web/src/entry.client' // .jsx,.tsx

const PATH_WEB_DIR_CONFIG_POSTCSS = 'web/config/postcss.config.js'
const PATH_WEB_DIR_CONFIG_STORYBOOK_CONFIG = 'web/config/storybook.config.js'
const PATH_WEB_DIR_CONFIG_STORYBOOK_PREVIEW = 'web/config/storybook.preview.js'
const PATH_WEB_DIR_CONFIG_STORYBOOK_PREVIEW = 'web/config/storybook.preview' // .js, .tsx
const PATH_WEB_DIR_CONFIG_STORYBOOK_MANAGER = 'web/config/storybook.manager.js'

const PATH_WEB_DIR_DIST = 'web/dist'

/**
Expand Down Expand Up @@ -205,9 +204,8 @@ export const getPaths = (BASE_DIR: string = getBaseDir()): Paths => {
BASE_DIR,
PATH_WEB_DIR_CONFIG_STORYBOOK_CONFIG
),
storybookPreviewConfig: path.join(
BASE_DIR,
PATH_WEB_DIR_CONFIG_STORYBOOK_PREVIEW
storybookPreviewConfig: resolveFile(
path.join(BASE_DIR, PATH_WEB_DIR_CONFIG_STORYBOOK_PREVIEW)
),
storybookManagerConfig: path.join(
BASE_DIR,
Expand Down
11 changes: 6 additions & 5 deletions packages/testing/config/storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,12 @@ const baseConfig = {
}
}

const userPreviewPath = fs.existsSync(
redwoodProjectPaths.web.storybookPreviewConfig
)
? redwoodProjectPaths.web.storybookPreviewConfig
: './preview.example.js'
let userPreviewPath = './preview.example.js'

if (redwoodProjectPaths.storybookPreviewConfig) {
userPreviewPath = redwoodProjectPaths.storybookPreviewConfig
}

sbConfig.resolve.alias['~__REDWOOD__USER_STORYBOOK_PREVIEW_CONFIG'] =
userPreviewPath

Expand Down

0 comments on commit eb70971

Please sign in to comment.