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
bnn1 and dac09 authored Dec 25, 2023
1 parent cb1176c commit ffb0a99
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 @@ -131,12 +131,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 @@ -411,12 +406,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 @@ -737,12 +727,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 @@ -1020,12 +1005,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 @@ -45,7 +45,7 @@ export interface WebPaths {
entries: string | null
postcss: string
storybookConfig: string
storybookPreviewConfig: string
storybookPreviewConfig: string | null
storybookManagerConfig: string
dist: string
distServer: string
Expand Down Expand Up @@ -119,9 +119,8 @@ const PATH_WEB_DIR_GRAPHQL = 'web/src/graphql' // .js,.ts

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'
const PATH_WEB_DIR_DIST_SERVER = 'web/dist/server'
const PATH_WEB_DIR_DIST_SERVER_ENTRY_SERVER = 'web/dist/server/entry.server.js'
Expand Down Expand Up @@ -229,9 +228,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

This comment has been minimized.

Copy link
@ChrisLFieldsII

ChrisLFieldsII Mar 5, 2024

This introduced a breaking change loading the storybook preview I believe. The new code always fails the if condition b/c it doesnt index the redwoodProjectPaths object properly. I am opening an issue for this as well.

Issue here

)
? 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 ffb0a99

Please sign in to comment.